diff --git a/CMakeLists.txt b/CMakeLists.txt index e0b88dc9..c342cb97 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,127 +1,130 @@ cmake_minimum_required(VERSION 3.0) project(kdeconnect) if (SAILFISHOS) set(KF5_MIN_VERSION "5.36.0") set(QT_MIN_VERSION "5.6.0") else() set(KF5_MIN_VERSION "5.70.0") set(QT_MIN_VERSION "5.10.0") endif() set(QCA_MIN_VERSION "2.1.0") find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) include(KDEInstallDirs) include(KDECompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) include(ECMAddTests) include(ECMSetupVersion) include(ECMInstallIcons) +include(ECMQtDeclareLoggingCategory) include(FeatureSummary) include(GenerateExportHeader) if (NOT SAILFISHOS) include(ECMQMLModules) endif() include(KDEConnectMacros.cmake) ecm_setup_version(1.4.0 VARIABLE_PREFIX KDECONNECT VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/kdeconnect-version.h ) if (SAILFISHOS) set(KF5_REQUIRED_COMPONENTS I18n CoreAddons Config) set(KF5_OPTIONAL_COMPONENTS) set(Qca_LIBRARY CONAN_PKG::Qca-qt5) add_definitions(-DSAILFISHOS) else() find_package(Qca-qt5 ${QCA_MIN_VERSION} REQUIRED) set(Qca_LIBRARY qca-qt5) set(KF5_REQUIRED_COMPONENTS I18n ConfigWidgets DBusAddons IconThemes Notifications KIO KCMUtils Service Solid Kirigami2 People) set(KF5_OPTIONAL_COMPONENTS DocTools) set_package_properties(KF5Kirigami2 PROPERTIES DESCRIPTION "QtQuick plugins to build user interfaces based on KDE UX guidelines" PURPOSE "Required for KDE Connect's QML-based GUI applications" URL "https://www.kde.org/products/kirigami/" TYPE RUNTIME ) if(UNIX AND NOT APPLE) find_package(KF5PulseAudioQt) endif() find_package(KF5PeopleVCard) set_package_properties(KF5PeopleVCard PROPERTIES PURPOSE "Read vcards from the file system" URL "https://invent.kde.org/pim/kpeoplevcard" TYPE RUNTIME ) add_definitions(-DHAVE_KIO) ecm_find_qmlmodule(org.kde.people 1.0) ecm_find_qmlmodule(QtQuick.Particles 2.0) endif() add_definitions(-DQT_NO_URL_CAST_FROM_STRING -DQT_NO_KEYWORDS -DQT_NO_CAST_FROM_ASCII) find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS DBus Quick Network Multimedia) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS ${KF5_REQUIRED_COMPONENTS}) if (KF5_OPTIONAL_COMPONENTS) find_package(KF5 ${KF5_MIN_VERSION} COMPONENTS ${KF5_OPTIONAL_COMPONENTS}) endif() if (NOT ZSH_AUTOCOMPLETE_DIR) set(ZSH_AUTOCOMPLETE_DIR "${KDE_INSTALL_DATADIR}/zsh/site-functions") endif() option(PRIVATE_DBUS_ENABLED "Use private dbus session for kdeconnect" OFF) if(PRIVATE_DBUS_ENABLED OR APPLE) add_compile_definitions(USE_PRIVATE_DBUS) endif() add_subdirectory(core) add_subdirectory(plugins) add_subdirectory(interfaces) if (NOT SAILFISHOS) add_subdirectory(icon) add_subdirectory(data) add_subdirectory(cli) add_subdirectory(declarativeplugin) add_subdirectory(kcm) add_subdirectory(kcmplugin) add_subdirectory(daemon) add_subdirectory(app) add_subdirectory(indicator) add_subdirectory(urlhandler) add_subdirectory(nautilus-extension) add_subdirectory(fileitemactionplugin) add_subdirectory(smsapp) add_subdirectory(settings) if(NOT WIN32) add_subdirectory(kio) add_subdirectory(plasmoid) endif() endif() if(KF5DocTools_FOUND) add_subdirectory(doc) endif() if(BUILD_TESTING) add_subdirectory(tests) endif() +ecm_qt_install_logging_categories(EXPORT kdeconnect-kde FILE kdeconnect-kde.categories SORT DESTINATION "${KDE_INSTALL_LOGGINGCATEGORIESDIR}") + feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 40fc61c5..92f57a12 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -1,79 +1,88 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdeconnect-core\") +set(debug_file_SRCS) + +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER kdeconnect_debug.h + IDENTIFIER KDECONNECT_CORE CATEGORY_NAME kdeconnect.core + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (core)") + set(KDECONNECT_PRIVATE_DBUS_ADDR unix:tmpdir=/tmp) if(WIN32) set(KDECONNECT_PRIVATE_DBUS_ADDR tcp:host=localhost,port=0) endif() set(KDECONNECT_PRIVATE_DBUS_NAME DBusKDEConnectOnly) configure_file(dbushelper.h.in ${CMAKE_CURRENT_BINARY_DIR}/dbushelper.h) add_subdirectory(backends/lan) add_subdirectory(backends/loopback) option(BLUETOOTH_ENABLED "Bluetooth support for kdeconnect" OFF) if(BLUETOOTH_ENABLED) find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS Bluetooth) add_subdirectory(backends/bluetooth) endif() option(LOOPBACK_ENABLED "Loopback backend enabled" OFF) 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 networkpacket.cpp filetransferjob.cpp compositefiletransferjob.cpp daemon.cpp device.cpp core_debug.cpp notificationserverinfo.cpp + ${debug_file_SRCS} ) add_library(kdeconnectcore ${kdeconnectcore_SRCS}) target_include_directories(kdeconnectcore PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${PROJECT_SOURCE_DIR}) target_link_libraries(kdeconnectcore PUBLIC Qt5::Network KF5::CoreAddons ${Qca_LIBRARY} PRIVATE Qt5::DBus KF5::I18n KF5::ConfigCore ) if(${KF5KIO_FOUND}) target_link_libraries(kdeconnectcore PUBLIC KF5::KIOCore) endif() if (BLUETOOTH_ENABLED) target_compile_definitions(kdeconnectcore PRIVATE -DKDECONNECT_BLUETOOTH) target_link_libraries(kdeconnectcore PRIVATE Qt5::Bluetooth) endif() if (LOOPBACK_ENABLED) target_compile_definitions(kdeconnectcore PRIVATE -DKDECONNECT_LOOPBACK) endif() set_target_properties(kdeconnectcore PROPERTIES VERSION ${KDECONNECT_VERSION} SOVERSION ${KDECONNECT_VERSION_MAJOR} ) 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/core_debug.cpp b/core/core_debug.cpp index c82f65d7..a6ccc75e 100644 --- a/core/core_debug.cpp +++ b/core/core_debug.cpp @@ -1,40 +1,38 @@ /** * 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 "core_debug.h" -Q_LOGGING_CATEGORY(KDECONNECT_CORE, "kdeconnect.core") - #if defined(__GNU_LIBRARY__) #include #include #include #endif void logBacktrace() { #if defined(__GNU_LIBRARY__) 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 } diff --git a/core/core_debug.h b/core/core_debug.h index 3daf0f72..cb9a6bf3 100644 --- a/core/core_debug.h +++ b/core/core_debug.h @@ -1,32 +1,29 @@ /** * 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 - #include "kdeconnectcore_export.h" - -KDECONNECTCORE_EXPORT Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_CORE) +#include "kdeconnect_debug.h" void logBacktrace(); #endif //CORE_DEBUG_H diff --git a/daemon/CMakeLists.txt b/daemon/CMakeLists.txt index 5d81ef3b..310ad609 100644 --- a/daemon/CMakeLists.txt +++ b/daemon/CMakeLists.txt @@ -1,16 +1,23 @@ add_definitions(-DTRANSLATION_DOMAIN="kdeconnect-kded") -add_executable(kdeconnectd kdeconnectd.cpp) +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER kdeconnectd_debug.h + IDENTIFIER KDECONNECT_DAEMON CATEGORY_NAME kdeconnect.daemon + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (daemon)") + +add_executable(kdeconnectd kdeconnectd.cpp ${debug_file_SRCS}) target_include_directories(kdeconnectd PUBLIC ${CMAKE_BINARY_DIR}) target_link_libraries(kdeconnectd kdeconnectcore KF5::KIOWidgets KF5::DBusAddons KF5::Notifications KF5::I18n Qt5::Widgets) ecm_mark_nongui_executable(kdeconnectd) configure_file(org.kde.kdeconnect.daemon.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kdeconnect.daemon.desktop) configure_file(org.kde.kdeconnect.service.in ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kdeconnect.service) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kdeconnect.daemon.desktop DESTINATION ${AUTOSTART_INSTALL_DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kdeconnect.daemon.desktop DESTINATION ${XDG_APPS_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/kdeconnectd.cpp b/daemon/kdeconnectd.cpp index 29d72b19..c717d58d 100644 --- a/daemon/kdeconnectd.cpp +++ b/daemon/kdeconnectd.cpp @@ -1,189 +1,186 @@ /** * 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 #include #include #include #include #include #include "core/daemon.h" #include "core/device.h" #include "core/backends/pairinghandler.h" #include "kdeconnect-version.h" - -Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_DAEMON) -Q_LOGGING_CATEGORY(KDECONNECT_DAEMON, "kdeconnect.daemon") +#include "kdeconnectd_debug.h" class DesktopDaemon : public Daemon { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.daemon") public: DesktopDaemon(QObject* parent = nullptr) : Daemon(parent) , m_nam(nullptr) { qApp->setWindowIcon(QIcon(QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, QStringLiteral("icons/hicolor/scalable/apps/kdeconnect.svg")))); } void askPairingConfirmation(Device* device) override { KNotification* notification = new KNotification(QStringLiteral("pairingRequest"), KNotification::NotificationFlag::Persistent); QTimer::singleShot(PairingHandler::pairingTimeoutMsec(), notification, &KNotification::close); notification->setIconName(QStringLiteral("dialog-information")); notification->setComponentName(QStringLiteral("kdeconnect")); notification->setTitle(QStringLiteral("KDE Connect")); notification->setText(i18n("Pairing request from %1", device->name().toHtmlEscaped())); notification->setDefaultAction(i18n("Open")); notification->setActions(QStringList() << i18n("Accept") << i18n("Reject")); connect(notification, &KNotification::action1Activated, device, &Device::acceptPairing); connect(notification, &KNotification::action2Activated, device, &Device::rejectPairing); connect(notification, QOverload<>::of(&KNotification::activated), this, []{ QProcess::startDetached(QStringLiteral("kdeconnect-settings")); }); notification->sendEvent(); } void reportError(const QString & title, const QString & description) override { qCWarning(KDECONNECT_DAEMON) << title << ":" << description; KNotification::event(KNotification::Error, title, description); } QNetworkAccessManager* networkAccessManager() override { if (!m_nam) { m_nam = new KIO::AccessManager(this); } return m_nam; } KJobTrackerInterface* jobTracker() override { return KIO::getJobTracker(); } Q_SCRIPTABLE void sendSimpleNotification(const QString &eventId, const QString &title, const QString &text, const QString &iconName) override { KNotification* notification = new KNotification(eventId); //KNotification::Persistent notification->setIconName(iconName); notification->setComponentName(QStringLiteral("kdeconnect")); notification->setTitle(title); notification->setText(text); notification->sendEvent(); } void quit() override { QApplication::quit(); } private: QNetworkAccessManager* m_nam; }; // Copied from plasma-workspace/libkworkspace/kworkspace.cpp static void detectPlatform(int argc, char **argv) { if (qEnvironmentVariableIsSet("QT_QPA_PLATFORM")) { return; } for (int i = 0; i < argc; i++) { if (qstrcmp(argv[i], "-platform") == 0 || qstrcmp(argv[i], "--platform") == 0 || QByteArray(argv[i]).startsWith("-platform=") || QByteArray(argv[i]).startsWith("--platform=")) { return; } } const QByteArray sessionType = qgetenv("XDG_SESSION_TYPE"); if (sessionType.isEmpty()) { return; } if (qstrcmp(sessionType, "wayland") == 0) { qputenv("QT_QPA_PLATFORM", "wayland"); } else if (qstrcmp(sessionType, "x11") == 0) { qputenv("QT_QPA_PLATFORM", "xcb"); } } int main(int argc, char* argv[]) { detectPlatform(argc, argv); QApplication app(argc, argv); KAboutData aboutData( QStringLiteral("kdeconnect.daemon"), i18n("KDE Connect Daemon"), QStringLiteral(KDECONNECT_VERSION_STRING), i18n("KDE Connect Daemon"), KAboutLicense::GPL ); KAboutData::setApplicationData(aboutData); app.setQuitOnLastWindowClosed(false); #ifdef USE_PRIVATE_DBUS DBusHelper::launchDBusDaemon(); #endif QCommandLineParser parser; QCommandLineOption replaceOption({QStringLiteral("replace")}, i18n("Replace an existing instance")); parser.addOption(replaceOption); aboutData.setupCommandLine(&parser); parser.process(app); aboutData.processCommandLine(&parser); if (parser.isSet(replaceOption)) { auto message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), QStringLiteral("/MainApplication"), QStringLiteral("org.qtproject.Qt.QCoreApplication"), QStringLiteral("quit")); DBusHelper::sessionBus().call(message); //deliberately block until it's done, so we register the name after the app quits } KDBusService dbusService(KDBusService::Unique); DesktopDaemon daemon; // kdeconnectd is autostarted, so disable session management to speed up startup auto disableSessionManagement = [](QSessionManager &sm) { sm.setRestartHint(QSessionManager::RestartNever); }; QObject::connect(&app, &QGuiApplication::commitDataRequest, disableSessionManagement); QObject::connect(&app, &QGuiApplication::saveStateRequest, disableSessionManagement); return app.exec(); } #include "kdeconnectd.moc" diff --git a/fileitemactionplugin/CMakeLists.txt b/fileitemactionplugin/CMakeLists.txt index d6b29f11..dd980bbd 100644 --- a/fileitemactionplugin/CMakeLists.txt +++ b/fileitemactionplugin/CMakeLists.txt @@ -1,11 +1,18 @@ add_definitions(-DTRANSLATION_DOMAIN="kdeconnect-fileitemaction") -add_library(kdeconnectfileitemaction MODULE sendfileitemaction.cpp) +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER kdeconnect_fileitemaction_debug.h + IDENTIFIER KDECONNECT_FILEITEMACTION CATEGORY_NAME kdeconnect.fileitemaction + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (fileitemaction)") + +add_library(kdeconnectfileitemaction MODULE sendfileitemaction.cpp ${debug_file_SRCS}) target_link_libraries( kdeconnectfileitemaction KF5::KIOWidgets KF5::I18n kdeconnectinterfaces kdeconnectcore ) install(TARGETS kdeconnectfileitemaction DESTINATION ${PLUGIN_INSTALL_DIR}/kf5/kfileitemaction) diff --git a/fileitemactionplugin/sendfileitemaction.cpp b/fileitemactionplugin/sendfileitemaction.cpp index bf8e1733..9c8670b6 100644 --- a/fileitemactionplugin/sendfileitemaction.cpp +++ b/fileitemactionplugin/sendfileitemaction.cpp @@ -1,101 +1,101 @@ /* * 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 #include -K_PLUGIN_CLASS_WITH_JSON(SendFileItemAction, "kdeconnectsendfile.json") +#include "kdeconnect_fileitemaction_debug.h" -Q_LOGGING_CATEGORY(KDECONNECT_FILEITEMACTION, "kdeconnect.fileitemaction") +K_PLUGIN_CLASS_WITH_JSON(SendFileItemAction, "kdeconnectsendfile.json") 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(); for (const QString& id : devices) { DeviceDbusInterface deviceIface(id); if (!deviceIface.isValid()) { continue; } 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, &QAction::triggered, this, &SendFileItemAction::sendFile); actions += action; } if (actions.count() > 1) { QAction* menuAction = new QAction(QIcon::fromTheme(QStringLiteral("kdeconnect")), 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() { const QList urls = sender()->property("urls").value>(); QString id = sender()->property("id").toString(); for (const QUrl& url : urls) { QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), QStringLiteral("/modules/kdeconnect/devices/") + id + QStringLiteral("/share"), QStringLiteral("org.kde.kdeconnect.device.share"), QStringLiteral("shareUrl")); msg.setArguments(QVariantList() << url.toString()); DBusHelper::sessionBus().asyncCall(msg); } } #include "sendfileitemaction.moc" diff --git a/fileitemactionplugin/sendfileitemaction.h b/fileitemactionplugin/sendfileitemaction.h index 6020a23d..1fea7c10 100644 --- a/fileitemactionplugin/sendfileitemaction.h +++ b/fileitemactionplugin/sendfileitemaction.h @@ -1,45 +1,43 @@ /* * 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); QList< QAction* > actions(const KFileItemListProperties& fileItemInfos, QWidget* parentWidget) override; private Q_SLOTS: void sendFile(); }; #endif // SENDFILEITEMACTION_H diff --git a/interfaces/CMakeLists.txt b/interfaces/CMakeLists.txt index 389ca029..7912a6dd 100644 --- a/interfaces/CMakeLists.txt +++ b/interfaces/CMakeLists.txt @@ -1,61 +1,75 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdeconnect-interfaces\") function(geninterface source_h output_h) set(xml_file ${CMAKE_CURRENT_BINARY_DIR}/${output_h}.xml) qt5_generate_dbus_interface( ${source_h} ${xml_file}) set_source_files_properties(${xml_file} PROPERTIES NO_NAMESPACE true) qt5_add_dbus_interface(libkdeconnect_SRC ${xml_file} ${output_h}) set(libkdeconnect_SRC ${libkdeconnect_SRC} PARENT_SCOPE) endfunction() +set(debug_files_SRCS) +ecm_qt_declare_logging_category( + debug_files_SRCS HEADER interfaces_conversation_message_debug.h + IDENTIFIER CONVERSATION_MESSAGE_LOGGING_CATEGORY CATEGORY_NAME kdeconnect.interfaces.conversationmessage + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (conversation message)") + +ecm_qt_declare_logging_category( + debug_files_SRCS HEADER interfaces_debug.h + IDENTIFIER KDECONNECT_INTERFACES CATEGORY_NAME kdeconnect.interfaces + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (interfaces)") + set(libkdeconnect_SRC dbusinterfaces.cpp devicesmodel.cpp notificationsmodel.cpp devicessortproxymodel.cpp conversationmessage.cpp remotecommandsmodel.cpp remotesinksmodel.cpp devicespluginfilterproxymodel.cpp # modeltest.cpp + ${debug_files_SRCS} ) geninterface(${PROJECT_SOURCE_DIR}/core/daemon.h daemoninterface) geninterface(${PROJECT_SOURCE_DIR}/core/device.h deviceinterface) geninterface(${PROJECT_SOURCE_DIR}/plugins/battery/batterydbusinterface.h devicebatteryinterface) geninterface(${PROJECT_SOURCE_DIR}/plugins/sftp/sftpplugin.h devicesftpinterface) geninterface(${PROJECT_SOURCE_DIR}/plugins/notifications/notificationsdbusinterface.h devicenotificationsinterface) geninterface(${PROJECT_SOURCE_DIR}/plugins/findmyphone/findmyphoneplugin.h devicefindmyphoneinterface) geninterface(${PROJECT_SOURCE_DIR}/plugins/notifications/notification.h notificationinterface) geninterface(${PROJECT_SOURCE_DIR}/plugins/mprisremote/mprisremoteplugin.h mprisremoteinterface) geninterface(${PROJECT_SOURCE_DIR}/plugins/remotecontrol/remotecontrolplugin.h remotecontrolinterface) geninterface(${PROJECT_SOURCE_DIR}/plugins/lockdevice/lockdeviceplugin.h lockdeviceinterface) geninterface(${PROJECT_SOURCE_DIR}/plugins/remotecommands/remotecommandsplugin.h remotecommandsinterface) geninterface(${PROJECT_SOURCE_DIR}/plugins/remotekeyboard/remotekeyboardplugin.h remotekeyboardinterface) geninterface(${PROJECT_SOURCE_DIR}/plugins/sms/smsplugin.h smsinterface) geninterface(${PROJECT_SOURCE_DIR}/plugins/sms/conversationsdbusinterface.h conversationsinterface) geninterface(${PROJECT_SOURCE_DIR}/plugins/share/shareplugin.h shareinterface) geninterface(${PROJECT_SOURCE_DIR}/plugins/remotesystemvolume/remotesystemvolumeplugin.h remotesystemvolumeinterface) geninterface(${PROJECT_SOURCE_DIR}/plugins/bigscreen/bigscreenplugin.h bigscreeninterface) add_library(kdeconnectinterfaces ${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 kdeconnectcore ) install(TARGETS kdeconnectinterfaces EXPORT kdeconnectLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY NAMELINK_SKIP) diff --git a/interfaces/conversationmessage.cpp b/interfaces/conversationmessage.cpp index 47234ca2..4b2a4fcd 100644 --- a/interfaces/conversationmessage.cpp +++ b/interfaces/conversationmessage.cpp @@ -1,162 +1,161 @@ /** * Copyright 2018 Simon Redman * * 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 "conversationmessage.h" -#include #include -Q_LOGGING_CATEGORY(CONVERSATION_MESSAGE_LOGGING_CATEGORY, "kdeconnect.interfaces.conversationmessage") +#include "interfaces_conversation_message_debug.h" ConversationMessage::ConversationMessage(const QVariantMap& args) : m_eventField(args[QStringLiteral("event")].toInt()), m_body(args[QStringLiteral("body")].toString()), m_date(args[QStringLiteral("date")].toLongLong()), m_type(args[QStringLiteral("type")].toInt()), m_read(args[QStringLiteral("read")].toInt()), m_threadID(args[QStringLiteral("thread_id")].toLongLong()), m_uID(args[QStringLiteral("_id")].toInt()) { QString test = QLatin1String(args[QStringLiteral("addresses")].typeName()); QVariantList jsonAddresses = args[QStringLiteral("addresses")].toList(); for (const QVariant& addressField : jsonAddresses) { const auto& rawAddress = addressField.toMap(); m_addresses.append(ConversationAddress(rawAddress[QStringLiteral("address")].value())); } QVariantMap::const_iterator subID_it = args.find(QStringLiteral("sub_id")); m_subID = subID_it == args.end() ? -1 : subID_it->toLongLong(); } ConversationMessage::ConversationMessage (const qint32& eventField, const QString& body, const QList& addresses, const qint64& date, const qint32& type, const qint32& read, const qint64& threadID, const qint32& uID, const qint64& subID) : m_eventField(eventField) , m_body(body) , m_addresses(addresses) , m_date(date) , m_type(type) , m_read(read) , m_threadID(threadID) , m_uID(uID) , m_subID(subID) { } ConversationMessage::ConversationMessage(const ConversationMessage& other) : m_eventField(other.m_eventField) , m_body(other.m_body) , m_addresses(other.m_addresses) , m_date(other.m_date) , m_type(other.m_type) , m_read(other.m_read) , m_threadID(other.m_threadID) , m_uID(other.m_uID) , m_subID(other.m_subID) { } ConversationMessage::~ConversationMessage() { } ConversationMessage& ConversationMessage::operator=(const ConversationMessage& other) { this->m_eventField = other.m_eventField; this->m_body = other.m_body; this->m_addresses = other.m_addresses; this->m_date = other.m_date; this->m_type = other.m_type; this->m_read = other.m_read; this->m_threadID = other.m_threadID; this->m_uID = other.m_uID; this->m_subID = other.m_subID; return *this; } ConversationMessage ConversationMessage::fromDBus(const QDBusVariant& var) { QDBusArgument data = var.variant().value(); ConversationMessage message; data >> message; return message; } QVariantMap ConversationMessage::toVariant() const { QVariantList addresses; for (const ConversationAddress& address : m_addresses) { addresses.push_back(address.toVariant()); } return { {QStringLiteral("event"), m_eventField}, {QStringLiteral("body"), m_body}, {QStringLiteral("addresses"), addresses}, {QStringLiteral("date"), m_date}, {QStringLiteral("type"), m_type}, {QStringLiteral("read"), m_read}, {QStringLiteral("thread_id"), m_threadID}, {QStringLiteral("_id"), m_uID}, {QStringLiteral("sub_id"), m_subID} }; } ConversationAddress::ConversationAddress(QString address) : m_address(address) {} ConversationAddress::ConversationAddress(const ConversationAddress& other) : m_address(other.address()) {} ConversationAddress::~ConversationAddress() {} ConversationAddress& ConversationAddress::operator=(const ConversationAddress& other) { this->m_address = other.m_address; return *this; } QList ConversationAddress::listfromDBus(const QDBusVariant& var) { QDBusArgument data = var.variant().value(); QList addresses; data >> addresses; return addresses; } QVariantMap ConversationAddress::toVariant() const { return { {QStringLiteral("address"), address()}, }; } void ConversationMessage::registerDbusType() { qDBusRegisterMetaType(); qRegisterMetaType(); qDBusRegisterMetaType(); qRegisterMetaType(); qDBusRegisterMetaType>(); qRegisterMetaType>(); } diff --git a/interfaces/conversationmessage.h b/interfaces/conversationmessage.h index bbf3060a..0287734d 100644 --- a/interfaces/conversationmessage.h +++ b/interfaces/conversationmessage.h @@ -1,233 +1,230 @@ /** * Copyright 2018 Simon Redman * * 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 PLUGINS_TELEPHONY_CONVERSATIONMESSAGE_H_ #define PLUGINS_TELEPHONY_CONVERSATIONMESSAGE_H_ #include -#include #include "kdeconnectinterfaces_export.h" -Q_DECLARE_LOGGING_CATEGORY(CONVERSATION_MESSAGE_LOGGING_CATEGORY) - class ConversationAddress; class KDECONNECTINTERFACES_EXPORT ConversationMessage { public: // TYPE field values from Android enum Types { MessageTypeAll = 0, MessageTypeInbox = 1, MessageTypeSent = 2, MessageTypeDraft = 3, MessageTypeOutbox = 4, MessageTypeFailed = 5, MessageTypeQueued = 6, }; /** * Values describing the possible type of events contained in a message * A message's eventField is constructed as a bitwise-OR of events * Any events which are unsupported should be ignored */ enum Events { EventTextMessage = 0x1, // This message has a body field which contains pure, human-readable text EventMultiTarget = 0x2, // This is a multitarget (group) message which has an "addresses" field which is a list of participants in the group }; /** * Build a new message from a keyword argument dictionary * * @param args mapping of field names to values as might be contained in a network packet containing a message */ ConversationMessage(const QVariantMap& args = QVariantMap()); ConversationMessage(const qint32& eventField, const QString& body, const QList& addresses, const qint64& date, const qint32& type, const qint32& read, const qint64& threadID, const qint32& uID, const qint64& subID); ConversationMessage(const ConversationMessage& other); ~ConversationMessage(); ConversationMessage& operator=(const ConversationMessage& other); static ConversationMessage fromDBus(const QDBusVariant&); static void registerDbusType(); qint32 eventField() const { return m_eventField; } QString body() const { return m_body; } QList addresses() const { return m_addresses; } qint64 date() const { return m_date; } qint32 type() const { return m_type; } qint32 read() const { return m_read; } qint64 threadID() const { return m_threadID; } qint32 uID() const { return m_uID; } qint64 subID() const { return m_subID; } QVariantMap toVariant() const; bool containsTextBody() const { return (eventField() & ConversationMessage::EventTextMessage); } bool isMultitarget() const { return (eventField() & ConversationMessage::EventMultiTarget); } bool isIncoming() const { return type() == MessageTypeInbox; } bool isOutgoing() const { return type() == MessageTypeSent; } /** * Return the address of the other party of a single-target conversation * Calling this method with a multi-target conversation is ill-defined */ QString getOtherPartyAddress() const; protected: /** * Bitwise OR of event flags * Unsupported flags shall cause the message to be ignored */ qint32 m_eventField; /** * Body of the message */ QString m_body; /** * List of all addresses involved in this conversation * An address is most likely a phone number, but may be something else like an email address */ QList m_addresses; /** * Date stamp (Unix epoch millis) associated with the message */ qint64 m_date; /** * Type of the message. See the message.type enum */ qint32 m_type; /** * Whether we have a read report for this message */ qint32 m_read; /** * Tag which binds individual messages into a thread */ qint64 m_threadID; /** * Value which uniquely identifies a message */ qint32 m_uID; /** * Value which determines SIM id (optional) */ qint64 m_subID; }; class KDECONNECTINTERFACES_EXPORT ConversationAddress { public: ConversationAddress(QString address = QStringLiteral()); ConversationAddress(const ConversationAddress& other); ~ConversationAddress(); ConversationAddress& operator=(const ConversationAddress& other); static QList listfromDBus(const QDBusVariant&); QString address() const { return m_address; } QVariantMap toVariant() const; private: QString m_address; }; inline QDBusArgument &operator<<(QDBusArgument &argument, const ConversationMessage &message) { argument.beginStructure(); argument << message.eventField() << message.body() << message.addresses() << message.date() << message.type() << message.read() << message.threadID() << message.uID() << message.subID(); argument.endStructure(); return argument; } inline const QDBusArgument &operator>>(const QDBusArgument &argument, ConversationMessage &message) { qint32 event; QString body; QList addresses; qint64 date; qint32 type; qint32 read; qint64 threadID; qint32 uID; qint64 m_subID; argument.beginStructure(); argument >> event; argument >> body; argument >> addresses; argument >> date; argument >> type; argument >> read; argument >> threadID; argument >> uID; argument >> m_subID; argument.endStructure(); message = ConversationMessage(event, body, addresses, date, type, read, threadID, uID, m_subID); return argument; } inline QDBusArgument& operator<<(QDBusArgument& argument, const ConversationAddress& address) { argument.beginStructure(); argument << address.address(); argument.endStructure(); return argument; } inline const QDBusArgument& operator>>(const QDBusArgument& argument, ConversationAddress& address) { QString addressField; argument.beginStructure(); argument >> addressField; argument.endStructure(); address = ConversationAddress(addressField); return argument; } Q_DECLARE_METATYPE(ConversationMessage) Q_DECLARE_METATYPE(ConversationAddress) #endif /* PLUGINS_TELEPHONY_CONVERSATIONMESSAGE_H_ */ diff --git a/interfaces/devicesmodel.cpp b/interfaces/devicesmodel.cpp index 7d456498..d0182c16 100644 --- a/interfaces/devicesmodel.cpp +++ b/interfaces/devicesmodel.cpp @@ -1,314 +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 "dbusinterfaces.h" #include // #include "modeltest.h" -Q_LOGGING_CATEGORY(KDECONNECT_INTERFACES, "kdeconnect.interfaces"); +#include "interfaces_debug.h" 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, &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, &OrgKdeKdeconnectDaemonInterface::deviceVisibilityChanged, this, &DevicesModel::deviceUpdated); connect(m_dbusInterface, &OrgKdeKdeconnectDaemonInterface::deviceRemoved, this, &DevicesModel::deviceRemoved); QDBusServiceWatcher* watcher = new QDBusServiceWatcher(DaemonDbusInterface::activatedService(), DBusHelper::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this); connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, &DevicesModel::refreshDeviceList); connect(watcher, &QDBusServiceWatcher::serviceUnregistered, this, &DevicesModel::clearDevices); //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()); 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) { // 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. // 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); } 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); 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); for (const QString& id : deviceIds) { appendDevice(new DeviceDbusInterface(id, this)); } endInsertRows(); } void DevicesModel::appendDevice(DeviceDbusInterface* dev) { m_deviceList.append(dev); 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 trusted = device->isTrusted(); bool reachable = device->isReachable(); 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->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 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/interfaces_debug.h b/interfaces/interfaces_debug.h deleted file mode 100644 index cfd1b648..00000000 --- a/interfaces/interfaces_debug.h +++ /dev/null @@ -1,28 +0,0 @@ -/** - * 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_INTERFACES_DEBUG_H -#define KDECONNECT_INTERFACES_DEBUG_H - -#include - -Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_INTERFACES) - -#endif //KDECONNECT_INTERFACES_DEBUG_H diff --git a/interfaces/notificationsmodel.cpp b/interfaces/notificationsmodel.cpp index 2af71836..17609ad4 100644 --- a/interfaces/notificationsmodel.cpp +++ b/interfaces/notificationsmodel.cpp @@ -1,270 +1,270 @@ /** * 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 "modeltest.h" //In older Qt released, qAsConst isnt available #include "core/qtcompat_p.h" +#include "interfaces_debug.h" NotificationsModel::NotificationsModel(QObject* parent) : QAbstractListModel(parent) , m_dbusInterface(nullptr) { //new ModelTest(this, this); connect(this, &QAbstractItemModel::rowsInserted, this, &NotificationsModel::rowsChanged); connect(this, &QAbstractItemModel::rowsRemoved, this, &NotificationsModel::rowsChanged); connect(this, &QAbstractItemModel::dataChanged, this, &NotificationsModel::anyDismissableChanged); connect(this, &QAbstractItemModel::rowsInserted, this, &NotificationsModel::anyDismissableChanged); QDBusServiceWatcher* watcher = new QDBusServiceWatcher(DaemonDbusInterface::activatedService(), DBusHelper::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this); connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, &NotificationsModel::refreshNotificationList); connect(watcher, &QDBusServiceWatcher::serviceUnregistered, this, &NotificationsModel::clearNotifications); } QHash NotificationsModel::roleNames() const { //Role names for QML QHash names = QAbstractItemModel::roleNames(); names.insert(DbusInterfaceRole, "dbusInterface"); names.insert(AppNameModelRole, "appName"); names.insert(IdModelRole, "notificationId"); names.insert(DismissableModelRole, "dismissable"); names.insert(RepliableModelRole, "repliable"); names.insert(IconPathModelRole, "appIcon"); names.insert(TitleModelRole, "title"); names.insert(TextModelRole, "notitext"); return names; } NotificationsModel::~NotificationsModel() { } QString NotificationsModel::deviceId() const { return m_deviceId; } void NotificationsModel::setDeviceId(const QString& deviceId) { m_deviceId = deviceId; if (m_dbusInterface) { delete m_dbusInterface; } m_dbusInterface = new DeviceNotificationsDbusInterface(deviceId, this); connect(m_dbusInterface, &OrgKdeKdeconnectDeviceNotificationsInterface::notificationPosted, this, &NotificationsModel::notificationAdded); connect(m_dbusInterface, &OrgKdeKdeconnectDeviceNotificationsInterface::notificationRemoved, this, &NotificationsModel::notificationRemoved); connect(m_dbusInterface, &OrgKdeKdeconnectDeviceNotificationsInterface::allNotificationsRemoved, this, &NotificationsModel::clearNotifications); refreshNotificationList(); Q_EMIT deviceIdChanged(deviceId); } void NotificationsModel::notificationAdded(const QString& id) { beginInsertRows(QModelIndex(), 0, 0); NotificationDbusInterface* dbusInterface = new NotificationDbusInterface(m_deviceId, id, this); connect(dbusInterface, &NotificationDbusInterface::ready, this, &NotificationsModel::notificationUpdated); m_notificationList.prepend(dbusInterface); endInsertRows(); } void NotificationsModel::notificationRemoved(const QString& id) { for (int i = 0; i < m_notificationList.size(); ++i) { if (m_notificationList[i]->notificationId() == id) { beginRemoveRows(QModelIndex(), i, i); m_notificationList.removeAt(i); endRemoveRows(); return; } } qCWarning(KDECONNECT_INTERFACES) << "Attempted to remove unknown notification: " << id; } void NotificationsModel::refreshNotificationList() { if (!m_dbusInterface) { return; } clearNotifications(); if (!m_dbusInterface->isValid()) { qCWarning(KDECONNECT_INTERFACES) << "dbus interface not valid"; return; } QDBusPendingReply pendingNotificationIds = m_dbusInterface->activeNotifications(); QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(pendingNotificationIds, this); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, &NotificationsModel::receivedNotifications); } void NotificationsModel::receivedNotifications(QDBusPendingCallWatcher* watcher) { watcher->deleteLater(); clearNotifications(); QDBusPendingReply pendingNotificationIds = *watcher; if (pendingNotificationIds.isError()) { qCWarning(KDECONNECT_INTERFACES) << pendingNotificationIds.error(); return; } const QStringList notificationIds = pendingNotificationIds.value(); if (notificationIds.isEmpty()) { return; } beginInsertRows(QModelIndex(), 0, notificationIds.size() - 1); for (const QString& notificationId : notificationIds) { NotificationDbusInterface* dbusInterface = new NotificationDbusInterface(m_deviceId, notificationId, this); m_notificationList.append(dbusInterface); } endInsertRows(); } QVariant NotificationsModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= m_notificationList.count() || !m_notificationList[index.row()]->isValid()) { return QVariant(); } if (!m_dbusInterface || !m_dbusInterface->isValid()) { return QVariant(); } NotificationDbusInterface* notification = m_notificationList[index.row()]; //FIXME: This function gets called lots of times, producing lots of dbus calls. Add a cache? switch (role) { case IconModelRole: return QIcon::fromTheme(QStringLiteral("device-notifier")); case IdModelRole: return notification->internalId(); case NameModelRole: return notification->ticker(); case ContentModelRole: return QString(); //To implement in the Android side case AppNameModelRole: return notification->appName(); case DbusInterfaceRole: return QVariant::fromValue(notification); case DismissableModelRole: return notification->dismissable(); case RepliableModelRole: return !notification->replyId().isEmpty(); case IconPathModelRole: return notification->iconPath(); case TitleModelRole: return notification->title(); case TextModelRole: return notification->text(); default: return QVariant(); } } NotificationDbusInterface* NotificationsModel::getNotification(const QModelIndex& index) const { if (!index.isValid()) { return nullptr; } int row = index.row(); if (row < 0 || row >= m_notificationList.size()) { return nullptr; } return m_notificationList[row]; } int NotificationsModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { //Return size 0 if we are a child because this is not a tree return 0; } return m_notificationList.count(); } bool NotificationsModel::isAnyDimissable() const { for (NotificationDbusInterface* notification : qAsConst(m_notificationList)) { if (notification->dismissable()) { return true; } } return false; } void NotificationsModel::dismissAll() { for (NotificationDbusInterface* notification : qAsConst(m_notificationList)) { if (notification->dismissable()) { notification->dismiss(); } } } void NotificationsModel::clearNotifications() { if (!m_notificationList.isEmpty()) { beginRemoveRows(QModelIndex(), 0, m_notificationList.size() - 1); qDeleteAll(m_notificationList); m_notificationList.clear(); endRemoveRows(); } } void NotificationsModel::notificationUpdated() { Q_EMIT dataChanged(index(0,0), index(m_notificationList.size() - 1, 0)); } diff --git a/kio/CMakeLists.txt b/kio/CMakeLists.txt index b16ccdd9..186dc2db 100644 --- a/kio/CMakeLists.txt +++ b/kio/CMakeLists.txt @@ -1,21 +1,29 @@ include_directories(${CMAKE_SOURCE_DIR}) add_definitions(-DTRANSLATION_DOMAIN="kdeconnect-kio") +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER kdeconnectkio_debug.h + IDENTIFIER KDECONNECT_KIO CATEGORY_NAME kdeconnect.kio + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (kio)") + set(kio_kdeconnect_PART_SRCS kiokdeconnect.cpp + ${debug_file_SRCS} ) add_library(kio_kdeconnect MODULE ${kio_kdeconnect_PART_SRCS}) target_link_libraries(kio_kdeconnect kdeconnectinterfaces Qt5::Core Qt5::Network KF5::KIOCore KF5::I18n ) ########### install files ############### set_target_properties(kio_kdeconnect PROPERTIES OUTPUT_NAME "kdeconnect") set_target_properties(kio_kdeconnect PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/kf5/kio") install(TARGETS kio_kdeconnect DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/kio) diff --git a/kio/kiokdeconnect.cpp b/kio/kiokdeconnect.cpp index 766dbd7a..ea7526e3 100644 --- a/kio/kiokdeconnect.cpp +++ b/kio/kiokdeconnect.cpp @@ -1,284 +1,284 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "kiokdeconnect.h" #include #include #include #include #include -Q_LOGGING_CATEGORY(KDECONNECT_KIO, "kdeconnect.kio") +#include "kdeconnectkio_debug.h" class KIOPluginForMetaData : public QObject { Q_OBJECT Q_PLUGIN_METADATA(IID "org.kde.kio.slave.kdeconnect" FILE "kdeconnect.json") }; extern "C" int Q_DECL_EXPORT kdemain(int argc, char** argv) { QCoreApplication app(argc, argv); app.setApplicationName(QStringLiteral("kio_kdeconnect")); 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_SLAVE_DEFINED; }; }; 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) : 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? const QStringList devices = m_dbusInterface->devices(true, true); for (const QString& deviceId : devices) { DeviceDbusInterface interface(deviceId); if (!interface.hasPlugin(QStringLiteral("kdeconnect_sftp"))) continue; const QString path = QStringLiteral("kdeconnect://").append(deviceId).append(QStringLiteral("/")); const QString name = interface.name(); const QString icon = QStringLiteral("kdeconnect"); KIO::UDSEntry entry; entry.reserve(6); entry.fastInsert(KIO::UDSEntry::UDS_NAME, name); entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, icon); entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, QT_STAT_DIR); entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, QFileDevice::ReadOwner | QFileDevice::ExeOwner | QFileDevice::ReadGroup | QFileDevice::ExeGroup | QFileDevice::ReadOther | QFileDevice::ExeOther); entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QLatin1String("")); entry.fastInsert(KIO::UDSEntry::UDS_URL, path); listEntry(entry); } // We also need a non-null and writable UDSentry for "." KIO::UDSEntry entry; entry.reserve(4); entry.fastInsert(KIO::UDSEntry::UDS_NAME, QStringLiteral(".")); entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, QT_STAT_DIR); entry.fastInsert(KIO::UDSEntry::UDS_SIZE, 0); entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner | QFileDevice::ReadGroup | QFileDevice::WriteGroup | QFileDevice::ExeGroup | QFileDevice::ReadOther | QFileDevice::ExeOther); listEntry(entry); infoMessage(QLatin1String("")); finished(); } void KioKdeconnect::listDevice(const QString& device) { infoMessage(i18n("Accessing device...")); qCDebug(KDECONNECT_KIO) << "ListDevice" << device; SftpDbusInterface interface(device); QDBusReply mountreply = interface.mountAndWait(); if (mountreply.error().type() == QDBusError::UnknownObject) { DaemonDbusInterface daemon; auto devsRepl = daemon.devices(false, false); devsRepl.waitForFinished(); if (!devsRepl.value().contains(device)) { error(KIO::ERR_SLAVE_DEFINED, i18n("No such device: %0").arg(device)); return; } DeviceDbusInterface dev(device); if (!dev.isTrusted()) { error(KIO::ERR_SLAVE_DEFINED, i18n("%0 is not paired").arg(dev.name())); return; } if (!dev.isReachable()) { error(KIO::ERR_SLAVE_DEFINED, i18n("%0 is not connected").arg(dev.name())); return; } if (!dev.hasPlugin(QStringLiteral("kdeconnect_sftp"))) { error(KIO::ERR_SLAVE_DEFINED, i18n("%0 has no Remote Filesystem plugin").arg(dev.name())); return; } } if (handleDBusError(mountreply, this)) { return; } if (!mountreply.value()) { error(KIO::ERR_SLAVE_DEFINED, interface.getMountError()); 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) { const QString path = it.key(); const QString name = it.value().toString(); const QString icon = QStringLiteral("folder"); KIO::UDSEntry entry; entry.reserve(6); entry.fastInsert(KIO::UDSEntry::UDS_NAME, name); entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, icon); entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, QT_STAT_DIR); entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, QFileDevice::ReadOwner | QFileDevice::ExeOwner | QFileDevice::ReadGroup | QFileDevice::ExeGroup | QFileDevice::ReadOther | QFileDevice::ExeOther); entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QLatin1String("")); entry.fastInsert(KIO::UDSEntry::UDS_URL, QUrl::fromLocalFile(path).toString()); listEntry(entry); } // We also need a non-null and writable UDSentry for "." KIO::UDSEntry entry; entry.reserve(4); entry.fastInsert(KIO::UDSEntry::UDS_NAME, QStringLiteral(".")); entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, QT_STAT_DIR); entry.fastInsert(KIO::UDSEntry::UDS_SIZE, 0); entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner | QFileDevice::ReadGroup | QFileDevice::WriteGroup | QFileDevice::ExeGroup | QFileDevice::ReadOther | QFileDevice::ExeOther); listEntry(entry); infoMessage(QLatin1String("")); finished(); } void KioKdeconnect::listDir(const QUrl& url) { qCDebug(KDECONNECT_KIO) << "Listing..." << url; if (!m_dbusInterface->isValid()) { infoMessage(i18n("Could not contact background service.")); finished(); return; } QString currentDevice = url.host(); if (currentDevice.isEmpty()) { listAllDevices(); } else { listDevice(currentDevice); } } void KioKdeconnect::stat(const QUrl& url) { qCDebug(KDECONNECT_KIO) << "Stat: " << url; KIO::UDSEntry entry; entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, QT_STAT_DIR); QString currentDevice = url.host(); if (!currentDevice.isEmpty()) { SftpDbusInterface interface(currentDevice); if (interface.isValid()) { entry.fastInsert(KIO::UDSEntry::UDS_LOCAL_PATH, interface.mountPoint()); if (!interface.isMounted()) { interface.mount(); } } } statEntry(entry); finished(); } void KioKdeconnect::get(const QUrl& url) { qCDebug(KDECONNECT_KIO) << "Get: " << url; mimeType(QLatin1String("")); finished(); } //needed for JSON file embedding #include "kiokdeconnect.moc" diff --git a/kio/kiokdeconnect.h b/kio/kiokdeconnect.h index 28778bc1..6e012c95 100644 --- a/kio/kiokdeconnect.h +++ b/kio/kiokdeconnect.h @@ -1,57 +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 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 public: KioKdeconnect(const QByteArray& pool, const QByteArray& app); void get(const QUrl& url) override; void listDir(const QUrl& url) override; void stat(const QUrl& url) override; void listAllDevices(); //List all devices exported by m_dbusInterface void listDevice(const QString& device); //List m_currentDevice private: /** * KDED DBus interface, used to communicate to the daemon since we need some status (like connected) */ DaemonDbusInterface* m_dbusInterface; }; #endif diff --git a/plugins/battery/CMakeLists.txt b/plugins/battery/CMakeLists.txt index 00c5477b..e4b947b7 100644 --- a/plugins/battery/CMakeLists.txt +++ b/plugins/battery/CMakeLists.txt @@ -1,13 +1,21 @@ +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER plugin_battery_debug.h + IDENTIFIER KDECONNECT_PLUGIN_BATTERY CATEGORY_NAME kdeconnect.plugin.battery + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (plugin battery)") + set(kdeconnect_battery_SRCS batteryplugin.cpp batterydbusinterface.cpp + ${debug_file_SRCS} ) kdeconnect_add_plugin(kdeconnect_battery JSON kdeconnect_battery.json SOURCES ${kdeconnect_battery_SRCS}) target_link_libraries(kdeconnect_battery kdeconnectcore Qt5::DBus KF5::Solid KF5::I18n ) diff --git a/plugins/battery/batterydbusinterface.cpp b/plugins/battery/batterydbusinterface.cpp index f2bd3962..d598da8c 100644 --- a/plugins/battery/batterydbusinterface.cpp +++ b/plugins/battery/batterydbusinterface.cpp @@ -1,63 +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 "batterydbusinterface.h" #include "batteryplugin.h" #include #include +#include "plugin_battery_debug.h" QMap BatteryDbusInterface::s_dbusInterfaces; BatteryDbusInterface::BatteryDbusInterface(const Device* device) : QDBusAbstractAdaptor(const_cast(device)) , 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 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) { m_isCharging = isCharging; m_charge = currentCharge; Q_EMIT stateChanged(m_isCharging); Q_EMIT chargeChanged(m_charge); } diff --git a/plugins/battery/batteryplugin.cpp b/plugins/battery/batteryplugin.cpp index dc79e04a..7be80c20 100644 --- a/plugins/battery/batteryplugin.cpp +++ b/plugins/battery/batteryplugin.cpp @@ -1,155 +1,154 @@ /** * 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 #include #include "batterydbusinterface.h" +#include "plugin_battery_debug.h" K_PLUGIN_CLASS_WITH_JSON(BatteryPlugin, "kdeconnect_battery.json") -Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_BATTERY, "kdeconnect.plugin.battery") - BatteryPlugin::BatteryPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , batteryDbusInterface(new BatteryDbusInterface(device())) { //TODO: Our protocol should support a dynamic number of batteries. //Solid::DeviceNotifier *notifier = Solid::DeviceNotifier::instance(); } void BatteryPlugin::connected() { NetworkPacket np(PACKET_TYPE_BATTERY_REQUEST, {{QStringLiteral("request"),true}}); sendPacket(np); const auto batteryDevice = Solid::DeviceInterface::Type::Battery; const auto primary = Solid::Battery::BatteryType::PrimaryBattery; QList batteries = Solid::Device::listFromQuery(Solid::Predicate(batteryDevice, QStringLiteral("type"), primary)); if (batteries.isEmpty()) { qCWarning(KDECONNECT_PLUGIN_BATTERY) << "No Primary Battery detected on this system. This may be a bug."; QList allBatteries = Solid::Device::listFromType(batteryDevice); qCWarning(KDECONNECT_PLUGIN_BATTERY) << "Total quantity of batteries found: " << allBatteries.size(); return; } const Solid::Battery* chosen = batteries.first().as(); connect(chosen, &Solid::Battery::chargeStateChanged, this, &BatteryPlugin::chargeChanged); connect(chosen, &Solid::Battery::chargePercentChanged, this, &BatteryPlugin::chargeChanged); // Explicitly send the current charge chargeChanged(); } void BatteryPlugin::chargeChanged() { bool isAnyBatteryCharging = false; int batteryQuantity = 0; int cumulativeCharge = 0; const auto batteryDevice = Solid::DeviceInterface::Type::Battery; const auto primary = Solid::Battery::BatteryType::PrimaryBattery; QList batteries = Solid::Device::listFromQuery(Solid::Predicate(batteryDevice, QStringLiteral("type"), primary)); for (auto device : batteries) { const Solid::Battery* battery = device.as(); // Don't look at batteries that have been detached if (battery->isPresent()) { batteryQuantity++; cumulativeCharge += battery->chargePercent(); if (battery->chargeState() == Solid::Battery::ChargeState::Charging) { isAnyBatteryCharging = true; } } } if (batteryQuantity == 0) { qCWarning(KDECONNECT_PLUGIN_BATTERY) << "Primary Battery seems to have been removed. Suspending packets until it is reconnected."; return; } // Load a new Battery object to represent the first device in the list Solid::Battery* chosen = batteries.first().as(); // Prepare an outgoing network packet NetworkPacket status(PACKET_TYPE_BATTERY, {{}}); status.set(QStringLiteral("isCharging"), isAnyBatteryCharging); status.set(QStringLiteral("currentCharge"), cumulativeCharge / batteryQuantity); // FIXME: In future, we should consider sending an array of battery objects status.set(QStringLiteral("batteryQuantity"), batteryQuantity); // We consider primary battery to be low if it will only last for 5 minutes or // less. This doesn't necessarily work if (for example) Solid finds multiple // batteries. if (chosen->remainingTime() < 600 && chosen->chargeState() == Solid::Battery::ChargeState::Discharging) { status.set(QStringLiteral("thresholdEvent"), (int)ThresholdBatteryLow); } else { status.set(QStringLiteral("thresholdEvent"), (int)ThresholdNone); } sendPacket(status); } BatteryPlugin::~BatteryPlugin() { //FIXME: Qt dbus does not allow to remove an adaptor! (it causes a crash in // the next dbus access to its parent). The implication of not deleting this // is that disabling the plugin does not remove the interface (that will // return outdated values) and that enabling it again instantiates a second // adaptor. This is also a memory leak until the entire device is destroyed. //batteryDbusInterface->deleteLater(); } bool BatteryPlugin::receivePacket(const NetworkPacket& np) { bool isCharging = np.get(QStringLiteral("isCharging"), false); int currentCharge = np.get(QStringLiteral("currentCharge"), -1); int thresholdEvent = np.get(QStringLiteral("thresholdEvent"), (int)ThresholdNone); if (batteryDbusInterface->charge() != currentCharge || batteryDbusInterface->isCharging() != isCharging ) { batteryDbusInterface->updateValues(isCharging, currentCharge); } if ( thresholdEvent == ThresholdBatteryLow && !isCharging ) { Daemon::instance()->sendSimpleNotification(QStringLiteral("batteryLow"), i18nc("device name: low battery", "%1: Low Battery", device()->name()), i18n("Battery at %1%", currentCharge), QStringLiteral("battery-040")); } return true; } #include "batteryplugin.moc" diff --git a/plugins/battery/batteryplugin.h b/plugins/battery/batteryplugin.h index c631e88d..4970e6b2 100644 --- a/plugins/battery/batteryplugin.h +++ b/plugins/battery/batteryplugin.h @@ -1,62 +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 BATTERYPLUGIN_H #define BATTERYPLUGIN_H -#include #include #define PACKET_TYPE_BATTERY QStringLiteral("kdeconnect.battery") #define PACKET_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); ~BatteryPlugin() override; bool receivePacket(const NetworkPacket& np) override; void connected() override; // NB: This may be connected to zero or two signals in Solid::Battery - // chargePercentageChanged and chargeStatusChanged. // See inline comments for further details void chargeChanged(); 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/clipboard/CMakeLists.txt b/plugins/clipboard/CMakeLists.txt index 3481a9a1..30f62989 100644 --- a/plugins/clipboard/CMakeLists.txt +++ b/plugins/clipboard/CMakeLists.txt @@ -1,8 +1,16 @@ +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER plugin_clipboard_debug.h + IDENTIFIER KDECONNECT_PLUGIN_CLIPBOARD CATEGORY_NAME kdeconnect.plugin.clipboard + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (plugin clipboard)") + set(kdeconnect_clipboard_SRCS clipboardplugin.cpp clipboardlistener.cpp + ${debug_file_SRCS} ) 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/clipboard/clipboardplugin.cpp b/plugins/clipboard/clipboardplugin.cpp index bab72f5c..07884190 100644 --- a/plugins/clipboard/clipboardplugin.cpp +++ b/plugins/clipboard/clipboardplugin.cpp @@ -1,78 +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 "clipboardplugin.h" #include "clipboardlistener.h" +#include "plugin_clipboard_debug.h" #include K_PLUGIN_CLASS_WITH_JSON(ClipboardPlugin, "kdeconnect_clipboard.json") -Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_CLIPBOARD, "kdeconnect.plugin.clipboard") - ClipboardPlugin::ClipboardPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) { connect(ClipboardListener::instance(), &ClipboardListener::clipboardChanged, this, &ClipboardPlugin::propagateClipboard); } void ClipboardPlugin::connected() { sendConnectPacket(); } void ClipboardPlugin::propagateClipboard(const QString& content) { NetworkPacket np(PACKET_TYPE_CLIPBOARD, {{QStringLiteral("content"), content}}); sendPacket(np); } void ClipboardPlugin::sendConnectPacket() { NetworkPacket np(PACKET_TYPE_CLIPBOARD_CONNECT, { {QStringLiteral("content"), ClipboardListener::instance()->currentContent()}, {QStringLiteral("timestamp"), ClipboardListener::instance()->updateTimestamp()} }); sendPacket(np); } bool ClipboardPlugin::receivePacket(const NetworkPacket& np) { QString content = np.get(QStringLiteral("content")); if (np.type() == PACKET_TYPE_CLIPBOARD) { ClipboardListener::instance()->setText(content); return true; } else if (np.type() == PACKET_TYPE_CLIPBOARD_CONNECT) { qint64 packetTime = np.get(QStringLiteral("timestamp")); // If the packetTime is 0, it means the timestamp is unknown (so do nothing). if (packetTime == 0 || packetTime < ClipboardListener::instance()->updateTimestamp()) { return false; } ClipboardListener::instance()->setText(content); return true; } return false; } #include "clipboardplugin.moc" diff --git a/plugins/clipboard/clipboardplugin.h b/plugins/clipboard/clipboardplugin.h index 8d104b62..4722b2c4 100644 --- a/plugins/clipboard/clipboardplugin.h +++ b/plugins/clipboard/clipboardplugin.h @@ -1,72 +1,69 @@ /** * 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) - /** * Packet containing just clipboard contents, sent when a device updates its clipboard. *

* The body should look like so: * { * "content": "password" * } */ #define PACKET_TYPE_CLIPBOARD QStringLiteral("kdeconnect.clipboard") /** * Packet containing clipboard contents and a timestamp that the contents were last updated, sent * on first connection *

* The timestamp is milliseconds since epoch. It can be 0, which indicates that the clipboard * update time is currently unknown. *

* The body should look like so: * { * "timestamp": 542904563213, * "content": "password" * } */ #define PACKET_TYPE_CLIPBOARD_CONNECT QStringLiteral("kdeconnect.clipboard.connect") class ClipboardPlugin : public KdeConnectPlugin { Q_OBJECT public: explicit ClipboardPlugin(QObject* parent, const QVariantList& args); bool receivePacket(const NetworkPacket& np) override; void connected() override; private Q_SLOTS: void propagateClipboard(const QString& content); void sendConnectPacket(); }; #endif diff --git a/plugins/contacts/CMakeLists.txt b/plugins/contacts/CMakeLists.txt index b9df5237..492ebcb7 100644 --- a/plugins/contacts/CMakeLists.txt +++ b/plugins/contacts/CMakeLists.txt @@ -1,10 +1,18 @@ +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER plugin_contacts_debug.h + IDENTIFIER KDECONNECT_PLUGIN_CONTACTS CATEGORY_NAME kdeconnect.plugin.contacts + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (plugin contacts)") + set(kdeconnect_contacts_SRCS contactsplugin.cpp + ${debug_file_SRCS} ) kdeconnect_add_plugin(kdeconnect_contacts JSON kdeconnect_contacts.json SOURCES ${kdeconnect_contacts_SRCS}) target_link_libraries(kdeconnect_contacts kdeconnectcore Qt5::DBus ) diff --git a/plugins/contacts/contactsplugin.cpp b/plugins/contacts/contactsplugin.cpp index 3c7340e8..3c4062ae 100644 --- a/plugins/contacts/contactsplugin.cpp +++ b/plugins/contacts/contactsplugin.cpp @@ -1,212 +1,211 @@ /** * Copyright 2018 Simon Redman * * 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 -K_PLUGIN_CLASS_WITH_JSON(ContactsPlugin, "kdeconnect_contacts.json") +#include "plugin_contacts_debug.h" -Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_CONTACTS, "kdeconnect.plugin.contacts") +K_PLUGIN_CLASS_WITH_JSON(ContactsPlugin, "kdeconnect_contacts.json") ContactsPlugin::ContactsPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) { vcardsPath = QString(*vcardsLocation).append(QStringLiteral("/kdeconnect-").append(device()->id())); // Register custom types with dbus qRegisterMetaType("uID"); qDBusRegisterMetaType(); qRegisterMetaType("uIDList_t"); qDBusRegisterMetaType(); // Create the storage directory if it doesn't exist if (!QDir().mkpath(vcardsPath)) { qCWarning(KDECONNECT_PLUGIN_CONTACTS) << "Unable to create VCard directory"; } qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "Contacts constructor for device " << device()->name(); } void ContactsPlugin::connected() { synchronizeRemoteWithLocal(); } bool ContactsPlugin::receivePacket(const NetworkPacket& np) { //qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "Packet Received for device " << device()->name(); //qCDebug(KDECONNECT_PLUGIN_CONTACTS) << np.body(); if (np.type() == PACKAGE_TYPE_CONTACTS_RESPONSE_UIDS_TIMESTAMPS) { return handleResponseUIDsTimestamps(np); } else if (np.type() == PACKET_TYPE_CONTACTS_RESPONSE_VCARDS) { return handleResponseVCards(np); } else { // Is this check necessary? qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "Unknown packet type received from device: " << device()->name() << ". Maybe you need to upgrade KDE Connect?"; return false; } } void ContactsPlugin::synchronizeRemoteWithLocal() { sendRequest(PACKET_TYPE_CONTACTS_REQUEST_ALL_UIDS_TIMESTAMP); } bool ContactsPlugin::handleResponseUIDsTimestamps(const NetworkPacket& np) { if (!np.has(QStringLiteral("uids"))) { qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "handleResponseUIDsTimestamps:" << "Malformed packet does not have uids key"; return false; } uIDList_t uIDsToUpdate; QDir vcardsDir(vcardsPath); // Get a list of all file info in this directory // Clean out IDs returned from the remote. Anything leftover should be deleted QFileInfoList localVCards = vcardsDir.entryInfoList({QStringLiteral("*.vcard"), QStringLiteral("*.vcf")}); const QStringList& uIDs = np.get(QStringLiteral("uids")); // Check local storage for the contacts: // If the contact is not found in local storage, request its vcard be sent // If the contact is in local storage but not reported, delete it // If the contact is in local storage, compare its timestamp. If different, request the contact for (const QString& ID : uIDs) { QString filename = vcardsDir.filePath(ID + VCARD_EXTENSION); QFile vcardFile(filename); if (!QFile().exists(filename)) { // We do not have a vcard for this contact. Request it. uIDsToUpdate.push_back(ID); continue; } // Remove this file from the list of known files QFileInfo fileInfo(vcardFile); localVCards.removeOne(fileInfo); // Check if the vcard needs to be updated if (!vcardFile.open(QIODevice::ReadOnly)) { qCWarning(KDECONNECT_PLUGIN_CONTACTS) << "handleResponseUIDsTimestamps:" << "Unable to open" << filename << "to read even though it was reported to exist"; continue; } QTextStream fileReadStream(&vcardFile); QString line; while (!fileReadStream.atEnd()) { fileReadStream >> line; // TODO: Check that the saved ID is the same as the one we were expecting. This requires parsing the VCard if (!line.startsWith(QStringLiteral("X-KDECONNECT-TIMESTAMP:"))) { continue; } QStringList parts = line.split(QLatin1Char(':')); QString timestamp = parts[1]; qint64 remoteTimestamp = np.get(ID); qint64 localTimestamp = timestamp.toLongLong(); if (!(localTimestamp == remoteTimestamp)) { uIDsToUpdate.push_back(ID); } } } // Delete all locally-known files which were not reported by the remote device for (const QFileInfo& unknownFile : localVCards) { QFile toDelete(unknownFile.filePath()); toDelete.remove(); } sendRequestWithIDs(PACKET_TYPE_CONTACTS_REQUEST_VCARDS_BY_UIDS, uIDsToUpdate); return true; } bool ContactsPlugin::handleResponseVCards(const NetworkPacket& np) { if (!np.has(QStringLiteral("uids"))) { qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "handleResponseVCards:" << "Malformed packet does not have uids key"; return false; } QDir vcardsDir(vcardsPath); const QStringList& uIDs = np.get(QStringLiteral("uids")); // Loop over all IDs, extract the VCard from the packet and write the file for (const auto& ID : uIDs) { //qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "Got VCard:" << np.get(ID); QString filename = vcardsDir.filePath(ID + VCARD_EXTENSION); QFile vcardFile(filename); bool vcardFileOpened = vcardFile.open(QIODevice::WriteOnly); // Want to smash anything that might have already been there if (!vcardFileOpened) { qCWarning(KDECONNECT_PLUGIN_CONTACTS) << "handleResponseVCards:" << "Unable to open" << filename; continue; } QTextStream fileWriteStream(&vcardFile); const QString& vcard = np.get(ID); fileWriteStream << vcard; } qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "handleResponseVCards:" << "Got" << uIDs.size() << "VCards"; Q_EMIT localCacheSynchronized(uIDs); return true; } bool ContactsPlugin::sendRequest(const QString& packetType) { NetworkPacket np(packetType); bool success = sendPacket(np); qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "sendRequest: Sending " << packetType << success; return success; } bool ContactsPlugin::sendRequestWithIDs(const QString& packetType, const uIDList_t& uIDs) { NetworkPacket np(packetType); np.set(QStringLiteral("uids"), uIDs); bool success = sendPacket(np); return success; } QString ContactsPlugin::dbusPath() const { return QStringLiteral("/modules/kdeconnect/devices/") + device()->id() + QStringLiteral("/contacts"); } #include "contactsplugin.moc" diff --git a/plugins/findthisdevice/CMakeLists.txt b/plugins/findthisdevice/CMakeLists.txt index f32602ad..39e665c5 100644 --- a/plugins/findthisdevice/CMakeLists.txt +++ b/plugins/findthisdevice/CMakeLists.txt @@ -1,36 +1,44 @@ +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER plugin_findthisdevice_debug.h + IDENTIFIER KDECONNECT_PLUGIN_FINDTHISDEVICE CATEGORY_NAME kdeconnect.plugin.findthisdevice + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (plugin findthisdevice)") + set(kdeconnect_findthisdevice_SRCS findthisdeviceplugin.cpp + ${debug_file_SRCS} ) kdeconnect_add_plugin(kdeconnect_findthisdevice JSON kdeconnect_findthisdevice.json SOURCES ${kdeconnect_findthisdevice_SRCS}) target_link_libraries(kdeconnect_findthisdevice kdeconnectcore Qt5::Core Qt5::Multimedia Qt5::DBus ) if (NOT WIN32) target_link_libraries(kdeconnect_findthisdevice KF5::PulseAudioQt ) endif() set(kdeconnect_findthisdevice_config_SRCS findthisdevice_config.cpp) ki18n_wrap_ui(kdeconnect_findthisdevice_config_SRCS findthisdevice_config.ui) -add_library(kdeconnect_findthisdevice_config MODULE ${kdeconnect_findthisdevice_config_SRCS}) +add_library(kdeconnect_findthisdevice_config MODULE ${kdeconnect_findthisdevice_config_SRCS} ${debug_file_SRCS}) target_link_libraries(kdeconnect_findthisdevice_config kdeconnectpluginkcm Qt5::Multimedia KF5::I18n KF5::CoreAddons KF5::ConfigWidgets KF5::KIOWidgets # KUrlRequester ) install(TARGETS kdeconnect_findthisdevice_config DESTINATION ${KDE_INSTALL_PLUGINDIR}) install(FILES kdeconnect_findthisdevice_config.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) diff --git a/plugins/findthisdevice/findthisdeviceplugin.cpp b/plugins/findthisdevice/findthisdeviceplugin.cpp index 5d0df2ae..c868d6ed 100644 --- a/plugins/findthisdevice/findthisdeviceplugin.cpp +++ b/plugins/findthisdevice/findthisdeviceplugin.cpp @@ -1,90 +1,91 @@ /** * Copyright 2018 Friedrich W. H. Kossebau * Copyright 2019 Piyush Aggarwal * * 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 "findthisdeviceplugin.h" // KF #include #ifndef Q_OS_WIN #include #include #endif // Qt #include #include +#include "plugin_findthisdevice_debug.h" K_PLUGIN_CLASS_WITH_JSON(FindThisDevicePlugin, "kdeconnect_findthisdevice.json") FindThisDevicePlugin::FindThisDevicePlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) { } FindThisDevicePlugin::~FindThisDevicePlugin() = default; bool FindThisDevicePlugin::receivePacket(const NetworkPacket& np) { Q_UNUSED(np); const QString soundFile = config()->get(QStringLiteral("ringtone"), defaultSound()); const QUrl soundURL = QUrl::fromLocalFile(soundFile); if (soundURL.isEmpty()) { qCWarning(KDECONNECT_PLUGIN_FINDTHISDEVICE) << "Not playing sound, no valid ring tone specified."; return true; } QMediaPlayer* player = new QMediaPlayer; player->setAudioRole(QAudio::Role(QAudio::NotificationRole)); player->setMedia(soundURL); player->setVolume(100); #ifndef Q_OS_WIN const auto sinks = PulseAudioQt::Context::instance()->sinks(); QVector mutedSinks; for (auto sink : sinks) { if (sink->isMuted()) { sink->setMuted(false); mutedSinks.append(sink); } } connect(player, &QMediaPlayer::stateChanged, this, [player, mutedSinks]{ for (auto sink : qAsConst(mutedSinks)) { sink->setMuted(true); } }); #endif player->play(); connect(player, &QMediaPlayer::stateChanged, player, &QObject::deleteLater); // TODO: ensure to use built-in loudspeakers return true; } QString FindThisDevicePlugin::dbusPath() const { return QStringLiteral("/modules/kdeconnect/devices/") + device()->id() + QStringLiteral("/findthisdevice"); } #include "findthisdeviceplugin.moc" diff --git a/plugins/findthisdevice/findthisdeviceplugin.h b/plugins/findthisdevice/findthisdeviceplugin.h index 8b957c25..d0dbd89c 100644 --- a/plugins/findthisdevice/findthisdeviceplugin.h +++ b/plugins/findthisdevice/findthisdeviceplugin.h @@ -1,95 +1,89 @@ /** * Copyright 2018 Friedrich W. H. Kossebau * Copyright 2019 Piyush Aggarwal * * 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 FINDTHISDEVICEPLUGIN_H #define FINDTHISDEVICEPLUGIN_H #include #ifdef Q_OS_WIN #include #define INFO_BUFFER_SIZE 32767 #else #include #include #include #endif // Qt -#include +#include "plugin_findthisdevice_debug.h" #define PACKET_TYPE_FINDMYPHONE_REQUEST QStringLiteral("kdeconnect.findmyphone.request") -static const QLoggingCategory &KDECONNECT_PLUGIN_FINDTHISDEVICE() -{ - static const QLoggingCategory category("kdeconnect.plugin.findthisdevice"); - return category; -} - class FindThisDevicePlugin : public KdeConnectPlugin { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.findthisdevice") public: explicit FindThisDevicePlugin(QObject* parent, const QVariantList& args); ~FindThisDevicePlugin() override; void connected() override {}; QString dbusPath() const override; bool receivePacket(const NetworkPacket& np) override; }; inline QString defaultSound() { QString dirPath; QUrl soundURL; #ifdef Q_OS_WIN wchar_t infoBuf[INFO_BUFFER_SIZE]; if(!GetWindowsDirectory(infoBuf, INFO_BUFFER_SIZE)) { qCWarning(KDECONNECT_PLUGIN_FINDTHISDEVICE) << "Error with getting the Windows Directory."; } else { dirPath = QString::fromStdWString(infoBuf) + QStringLiteral("/media"); if (!dirPath.isEmpty()) { soundURL = QUrl::fromUserInput(QStringLiteral("Ring01.wav"), dirPath, QUrl::AssumeLocalFile); } } #else const QStringList dataLocations = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); for (const QString &dataLocation : dataLocations) { dirPath = dataLocation + QStringLiteral("/sounds"); soundURL = QUrl::fromUserInput(QStringLiteral("Oxygen-Im-Phone-Ring.ogg"), dirPath, QUrl::AssumeLocalFile); if ((soundURL.isLocalFile() && soundURL.isValid() && QFile::exists(soundURL.toLocalFile()))) { break; } } #endif if (soundURL.isEmpty()) { qCWarning(KDECONNECT_PLUGIN_FINDTHISDEVICE) << "Could not find default ring tone."; } return soundURL.toLocalFile(); } #endif //FINDTHISDEVICEPLUGIN_H diff --git a/plugins/lockdevice/CMakeLists.txt b/plugins/lockdevice/CMakeLists.txt index 2e7a9657..a6c25076 100644 --- a/plugins/lockdevice/CMakeLists.txt +++ b/plugins/lockdevice/CMakeLists.txt @@ -1,9 +1,16 @@ qt5_add_dbus_interface(lockdevice_SRCS org.freedesktop.ScreenSaver.xml screensaverdbusinterface) -kdeconnect_add_plugin(kdeconnect_lockdevice JSON kdeconnect_lockdevice.json SOURCES lockdeviceplugin.cpp ${lockdevice_SRCS}) +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER plugin_lock_debug.h + IDENTIFIER KDECONNECT_PLUGIN_LOCKREMOTE CATEGORY_NAME kdeconnect.plugin.lock + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (plugin lockremote)") + +kdeconnect_add_plugin(kdeconnect_lockdevice JSON kdeconnect_lockdevice.json SOURCES lockdeviceplugin.cpp ${lockdevice_SRCS} ${debug_file_SRCS}) target_link_libraries(kdeconnect_lockdevice kdeconnectcore Qt5::DBus KF5::I18n ) diff --git a/plugins/lockdevice/lockdeviceplugin.cpp b/plugins/lockdevice/lockdeviceplugin.cpp index da694551..cfdea719 100644 --- a/plugins/lockdevice/lockdeviceplugin.cpp +++ b/plugins/lockdevice/lockdeviceplugin.cpp @@ -1,103 +1,101 @@ /** * 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 "screensaverdbusinterface.h" +#include "plugin_lock_debug.h" #include #include K_PLUGIN_CLASS_WITH_JSON(LockDevicePlugin, "kdeconnect_lockdevice.json") -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) { NetworkPacket np(PACKET_TYPE_LOCK_REQUEST, {{QStringLiteral("setLocked"), locked}}); sendPacket(np); } bool LockDevicePlugin::receivePacket(const NetworkPacket & np) { 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(QStringLiteral("requestLocked")); if (np.has(QStringLiteral("setLocked"))) { iface()->SetActive(np.get(QStringLiteral("setLocked"))); sendState = true; } if (sendState) { NetworkPacket np(PACKET_TYPE_LOCK, QVariantMap {{QStringLiteral("isLocked"), QVariant::fromValue(iface()->GetActive())}}); sendPacket(np); } return true; } OrgFreedesktopScreenSaverInterface* LockDevicePlugin::iface() { if (!m_iface) { m_iface = new OrgFreedesktopScreenSaverInterface(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/org/freedesktop/ScreenSaver"), DBusHelper::sessionBus()); if(!m_iface->isValid()) qCWarning(KDECONNECT_PLUGIN_LOCKREMOTE) << "Couldn't connect to the ScreenSaver interface"; } return m_iface; } void LockDevicePlugin::connected() { NetworkPacket np(PACKET_TYPE_LOCK_REQUEST, {{QStringLiteral("requestLocked"), QVariant()}}); sendPacket(np); } QString LockDevicePlugin::dbusPath() const { return QStringLiteral("/modules/kdeconnect/devices/") + device()->id() + QStringLiteral("/lockdevice"); } #include "lockdeviceplugin.moc" diff --git a/plugins/mpriscontrol/CMakeLists.txt b/plugins/mpriscontrol/CMakeLists.txt index 9025b193..4ab748de 100644 --- a/plugins/mpriscontrol/CMakeLists.txt +++ b/plugins/mpriscontrol/CMakeLists.txt @@ -1,29 +1,36 @@ if(WIN32) set(kdeconnect_mpriscontrol_SRCS mpriscontrolplugin-win.cpp ) else() set(kdeconnect_mpriscontrol_SRCS mpriscontrolplugin.cpp ) set_source_files_properties( org.freedesktop.DBus.Properties.xml org.mpris.MediaPlayer2.Player.xml org.mpris.MediaPlayer2.xml PROPERTIES NO_NAMESPACE ON) qt5_add_dbus_interface(kdeconnect_mpriscontrol_SRCS org.freedesktop.DBus.Properties.xml dbusproperties) qt5_add_dbus_interface(kdeconnect_mpriscontrol_SRCS org.mpris.MediaPlayer2.Player.xml mprisplayer) qt5_add_dbus_interface(kdeconnect_mpriscontrol_SRCS org.mpris.MediaPlayer2.xml mprisroot) endif() -kdeconnect_add_plugin(kdeconnect_mpriscontrol JSON kdeconnect_mpriscontrol.json SOURCES ${kdeconnect_mpriscontrol_SRCS}) +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER plugin_mpris_debug.h + IDENTIFIER KDECONNECT_PLUGIN_MPRIS CATEGORY_NAME kdeconnect.plugin.mpris + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (plugin mpris)") + +kdeconnect_add_plugin(kdeconnect_mpriscontrol JSON kdeconnect_mpriscontrol.json SOURCES ${kdeconnect_mpriscontrol_SRCS} ${debug_file_SRCS}) if(WIN32) target_link_libraries(kdeconnect_mpriscontrol kdeconnectcore) else() target_link_libraries(kdeconnect_mpriscontrol Qt5::DBus kdeconnectcore) endif() diff --git a/plugins/mpriscontrol/mpriscontrolplugin-win.cpp b/plugins/mpriscontrol/mpriscontrolplugin-win.cpp index 4ccefa21..1326a76b 100644 --- a/plugins/mpriscontrol/mpriscontrolplugin-win.cpp +++ b/plugins/mpriscontrol/mpriscontrolplugin-win.cpp @@ -1,119 +1,118 @@ /** * Copyright 2018 Jun Bo Bi * * 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-win.h" #include +#include "plugin_mpris_debug.h" #include #include K_PLUGIN_CLASS_WITH_JSON(MprisControlPlugin, "kdeconnect_mpriscontrol.json") -Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_MPRIS, "kdeconnect.plugin.mpris") - MprisControlPlugin::MprisControlPlugin(QObject *parent, const QVariantList &args) : KdeConnectPlugin(parent, args) { } bool MprisControlPlugin::receivePacket(const NetworkPacket &np) { 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(QStringLiteral("player")); bool valid_player = (player == playername); if (!valid_player || np.get(QStringLiteral("requestPlayerList"))) { const QList playerlist = {playername}; NetworkPacket np(PACKET_TYPE_MPRIS); np.set(QStringLiteral("playerList"), playerlist); np.set(QStringLiteral("supportAlbumArtPayload"), false); sendPacket(np); if (!valid_player) { return true; } } if (np.has(QStringLiteral("action"))) { INPUT input={0}; input.type = INPUT_KEYBOARD; input.ki.time = 0; input.ki.dwExtraInfo = 0; input.ki.wScan = 0; input.ki.dwFlags = 0; if (np.has(QStringLiteral("action"))) { const QString& action = np.get(QStringLiteral("action")); if (action == QStringLiteral("PlayPause") || (action == QStringLiteral("Play")) || (action == QStringLiteral("Pause")) ) { input.ki.wVk = VK_MEDIA_PLAY_PAUSE; ::SendInput(1,&input,sizeof(INPUT)); } else if (action == QStringLiteral("Stop")) { input.ki.wVk = VK_MEDIA_STOP; ::SendInput(1,&input,sizeof(INPUT)); } else if (action == QStringLiteral("Next")) { input.ki.wVk = VK_MEDIA_NEXT_TRACK; ::SendInput(1,&input,sizeof(INPUT)); } else if (action == QStringLiteral("Previous")) { input.ki.wVk = VK_MEDIA_PREV_TRACK; ::SendInput(1,&input,sizeof(INPUT)); } else if (action == QStringLiteral("Stop")) { input.ki.wVk = VK_MEDIA_STOP; ::SendInput(1,&input,sizeof(INPUT)); } } } NetworkPacket answer(PACKET_TYPE_MPRIS); bool somethingToSend = false; if (np.get(QStringLiteral("requestNowPlaying"))) { answer.set(QStringLiteral("pos"), 0); answer.set(QStringLiteral("isPlaying"), false); answer.set(QStringLiteral("canPause"), false); answer.set(QStringLiteral("canPlay"), true); answer.set(QStringLiteral("canGoNext"), true); answer.set(QStringLiteral("canGoPrevious"), true); answer.set(QStringLiteral("canSeek"), false); somethingToSend = true; } if (np.get(QStringLiteral("requestVolume"))) { answer.set(QStringLiteral("volume"), 100); somethingToSend = true; } if (somethingToSend) { answer.set(QStringLiteral("player"), player); sendPacket(answer); } return true; } #include "mpriscontrolplugin-win.moc" diff --git a/plugins/mpriscontrol/mpriscontrolplugin-win.h b/plugins/mpriscontrol/mpriscontrolplugin-win.h index 0e1affaf..71c2286f 100644 --- a/plugins/mpriscontrol/mpriscontrolplugin-win.h +++ b/plugins/mpriscontrol/mpriscontrolplugin-win.h @@ -1,49 +1,46 @@ /** * Copyright 2018 Jun Bo Bi * * 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 MPRISCONTROLPLUGINWIN_H #define MPRISCONTROLPLUGINWIN_H #include #include -#include #define PLAYERNAME QStringLiteral("Media Player") #define PACKET_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); bool receivePacket(const NetworkPacket &np) override; void connected() override {} private: const QString playername = PLAYERNAME; }; #endif //MPRISCONTROLPLUGINWIN_H diff --git a/plugins/mpriscontrol/mpriscontrolplugin.cpp b/plugins/mpriscontrol/mpriscontrolplugin.cpp index dc2d1897..71b1b83e 100644 --- a/plugins/mpriscontrol/mpriscontrolplugin.cpp +++ b/plugins/mpriscontrol/mpriscontrolplugin.cpp @@ -1,398 +1,396 @@ /** * 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 "dbusproperties.h" #include "mprisplayer.h" #include "mprisroot.h" +#include "plugin_mpris_debug.h" K_PLUGIN_CLASS_WITH_JSON(MprisControlPlugin, "kdeconnect_mpriscontrol.json") -Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_MPRIS, "kdeconnect.plugin.mpris") - - MprisPlayer::MprisPlayer(const QString& serviceName, const QString& dbusObjectPath, const QDBusConnection& busConnection) : m_serviceName(serviceName) , m_propertiesInterface(new OrgFreedesktopDBusPropertiesInterface(serviceName, dbusObjectPath, busConnection)) , m_mediaPlayer2PlayerInterface(new OrgMprisMediaPlayer2PlayerInterface(serviceName, dbusObjectPath, busConnection)) { m_mediaPlayer2PlayerInterface->setTimeout(500); } MprisControlPlugin::MprisControlPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , prevVolume(-1) { m_watcher = new QDBusServiceWatcher(QString(), DBusHelper::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this); // TODO: QDBusConnectionInterface::serviceOwnerChanged is deprecated, maybe query org.freedesktop.DBus directly? connect(DBusHelper::sessionBus().interface(), &QDBusConnectionInterface::serviceOwnerChanged, this, &MprisControlPlugin::serviceOwnerChanged); //Add existing interfaces const QStringList services = DBusHelper::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(QStringLiteral("org.mpris.MediaPlayer2."))) return; if (serviceName.startsWith(QStringLiteral("org.mpris.MediaPlayer2.kdeconnect."))) return; // playerctld is a only a proxy to other media players, and can thus safely be ignored if (serviceName == QStringLiteral("org.mpris.MediaPlayer2.playerctld")) 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) { const QString mediaPlayerObjectPath = QStringLiteral("/org/mpris/MediaPlayer2"); OrgMprisMediaPlayer2Interface iface(service, mediaPlayerObjectPath, DBusHelper::sessionBus()); QString identity = iface.identity(); if (identity.isEmpty()) { identity = service.mid(sizeof("org.mpris.MediaPlayer2")); } QString uniqueName = identity; for (int i = 2; playerList.contains(uniqueName); ++i) { uniqueName = identity + QLatin1String(" [") + QString::number(i) + QLatin1Char(']'); } MprisPlayer player(service, mediaPlayerObjectPath, DBusHelper::sessionBus()); playerList.insert(uniqueName, player); connect(player.propertiesInterface(), &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged, this, &MprisControlPlugin::propertiesChanged); connect(player.mediaPlayer2PlayerInterface(), &OrgMprisMediaPlayer2PlayerInterface::Seeked, this, &MprisControlPlugin::seeked); qCDebug(KDECONNECT_PLUGIN_MPRIS) << "Mpris addPlayer" << service << "->" << uniqueName; sendPlayerList(); } void MprisControlPlugin::seeked(qlonglong position){ //qCDebug(KDECONNECT_PLUGIN_MPRIS) << "Seeked in player"; OrgMprisMediaPlayer2PlayerInterface* mediaPlayer2PlayerInterface = (OrgMprisMediaPlayer2PlayerInterface*)sender(); const auto end = playerList.constEnd(); const auto it = std::find_if(playerList.constBegin(), end, [mediaPlayer2PlayerInterface](const MprisPlayer& player) { return (player.mediaPlayer2PlayerInterface() == mediaPlayer2PlayerInterface); }); if (it == end) { qCWarning(KDECONNECT_PLUGIN_MPRIS) << "Seeked signal received for no longer tracked service" << mediaPlayer2PlayerInterface->service(); return; } const QString& playerName = it.key(); NetworkPacket np(PACKET_TYPE_MPRIS, { {QStringLiteral("pos"), position/1000}, //Send milis instead of nanos {QStringLiteral("player"), playerName} }); sendPacket(np); } void MprisControlPlugin::propertiesChanged(const QString& propertyInterface, const QVariantMap& properties) { Q_UNUSED(propertyInterface); OrgFreedesktopDBusPropertiesInterface* propertiesInterface = (OrgFreedesktopDBusPropertiesInterface*)sender(); const auto end = playerList.constEnd(); const auto it = std::find_if(playerList.constBegin(), end, [propertiesInterface](const MprisPlayer& player) { return (player.propertiesInterface() == propertiesInterface); }); if (it == end) { qCWarning(KDECONNECT_PLUGIN_MPRIS) << "PropertiesChanged signal received for no longer tracked service" << propertiesInterface->service(); return; } OrgMprisMediaPlayer2PlayerInterface* const mediaPlayer2PlayerInterface = it.value().mediaPlayer2PlayerInterface(); const QString& playerName = it.key(); NetworkPacket np(PACKET_TYPE_MPRIS); bool somethingToSend = false; if (properties.contains(QStringLiteral("Volume"))) { int volume = (int) (properties[QStringLiteral("Volume")].toDouble()*100); if (volume != prevVolume) { np.set(QStringLiteral("volume"),volume); prevVolume = volume; somethingToSend = true; } } if (properties.contains(QStringLiteral("Metadata"))) { QDBusArgument bullshit = qvariant_cast(properties[QStringLiteral("Metadata")]); QVariantMap nowPlayingMap; bullshit >> nowPlayingMap; mprisPlayerMetadataToNetworkPacket(np, nowPlayingMap); somethingToSend = true; } 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) { np.set(QStringLiteral("player"), playerName); // Always also update the position if (mediaPlayer2PlayerInterface->canSeek()) { long long pos = mediaPlayer2PlayerInterface->position(); np.set(QStringLiteral("pos"), pos/1000); //Send milis instead of nanos } sendPacket(np); } } void MprisControlPlugin::removePlayer(const QString& serviceName) { const auto end = playerList.end(); const auto it = std::find_if(playerList.begin(), end, [serviceName](const MprisPlayer& player) { return (player.serviceName() == serviceName); }); if (it == end) { qCWarning(KDECONNECT_PLUGIN_MPRIS) << "Could not find player for serviceName" << serviceName; return; } const QString& playerName = it.key(); qCDebug(KDECONNECT_PLUGIN_MPRIS) << "Mpris removePlayer" << serviceName << "->" << playerName; playerList.erase(it); sendPlayerList(); } bool MprisControlPlugin::sendAlbumArt(const NetworkPacket& np) { const QString player = np.get(QStringLiteral("player")); auto it = playerList.find(player); bool valid_player = (it != playerList.end()); if (!valid_player) { return false; } //Get mpris information auto& mprisInterface = *it.value().mediaPlayer2PlayerInterface(); QVariantMap nowPlayingMap = mprisInterface.metadata(); //Check if the supplied album art url indeed belongs to this mpris player QUrl playerAlbumArtUrl{nowPlayingMap[QStringLiteral("mpris:artUrl")].toString()}; QString requestedAlbumArtUrl = np.get(QStringLiteral("albumArtUrl")); if (!playerAlbumArtUrl.isValid() || playerAlbumArtUrl != QUrl(requestedAlbumArtUrl)) { return false; } //Only support sending local files if (playerAlbumArtUrl.scheme() != QStringLiteral("file")) { return false; } //Open the file to send QSharedPointer art{new QFile(playerAlbumArtUrl.toLocalFile())}; //Send the album art as payload NetworkPacket answer(PACKET_TYPE_MPRIS); answer.set(QStringLiteral("transferringAlbumArt"), true); answer.set(QStringLiteral("player"), player); answer.set(QStringLiteral("albumArtUrl"), requestedAlbumArtUrl); answer.setPayload(art, art->size()); sendPacket(answer); return true; } bool MprisControlPlugin::receivePacket (const NetworkPacket& np) { if (np.has(QStringLiteral("playerList"))) { return false; //Whoever sent this is an mpris client and not an mpris control! } if (np.has(QStringLiteral("albumArtUrl"))) { return sendAlbumArt(np); } //Send the player list const QString player = np.get(QStringLiteral("player")); auto it = playerList.find(player); bool valid_player = (it != playerList.end()); if (!valid_player || np.get(QStringLiteral("requestPlayerList"))) { sendPlayerList(); if (!valid_player) { return true; } } //Do something to the mpris interface const QString& serviceName = it.value().serviceName(); // turn from pointer to reference to keep the patch diff small, // actual patch would change all "mprisInterface." into "mprisInterface->" auto& mprisInterface = *it.value().mediaPlayer2PlayerInterface(); if (np.has(QStringLiteral("action"))) { const QString& action = np.get(QStringLiteral("action")); //qCDebug(KDECONNECT_PLUGIN_MPRIS) << "Calling action" << action << "in" << serviceName; //TODO: Check for valid actions, currently we trust anything the other end sends us mprisInterface.call(action); } if (np.has(QStringLiteral("setVolume"))) { double volume = np.get(QStringLiteral("setVolume"))/100.f; qCDebug(KDECONNECT_PLUGIN_MPRIS) << "Setting volume" << volume << "to" << serviceName; mprisInterface.setVolume(volume); } if (np.has(QStringLiteral("Seek"))) { int offset = np.get(QStringLiteral("Seek")); //qCDebug(KDECONNECT_PLUGIN_MPRIS) << "Seeking" << offset << "to" << serviceName; mprisInterface.Seek(offset); } 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" << serviceName; mprisInterface.Seek(seek); } //Send something read from the mpris interface NetworkPacket answer(PACKET_TYPE_MPRIS); bool somethingToSend = false; if (np.get(QStringLiteral("requestNowPlaying"))) { QVariantMap nowPlayingMap = mprisInterface.metadata(); mprisPlayerMetadataToNetworkPacket(answer, nowPlayingMap); qlonglong pos = mprisInterface.position(); answer.set(QStringLiteral("pos"), pos/1000); bool playing = (mprisInterface.playbackStatus() == QLatin1String("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(QStringLiteral("requestVolume"))) { int volume = (int)(mprisInterface.volume() * 100); answer.set(QStringLiteral("volume"),volume); somethingToSend = true; } if (somethingToSend) { answer.set(QStringLiteral("player"), player); sendPacket(answer); } return true; } void MprisControlPlugin::sendPlayerList() { NetworkPacket np(PACKET_TYPE_MPRIS); np.set(QStringLiteral("playerList"),playerList.keys()); np.set(QStringLiteral("supportAlbumArtPayload"), true); sendPacket(np); } void MprisControlPlugin::mprisPlayerMetadataToNetworkPacket(NetworkPacket& 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(); QUrl fileUrl = nowPlayingMap[QStringLiteral("xesam:url")].toUrl(); if (title.isEmpty() && artist.isEmpty() && fileUrl.isLocalFile()) { title = fileUrl.fileName(); QStringList splitUrl = fileUrl.path().split(QDir::separator()); if (album.isEmpty() && splitUrl.size() > 1) { album = splitUrl.at(splitUrl.size() - 2); } } QString nowPlaying = title; if (!artist.isEmpty()) { nowPlaying = artist + QStringLiteral(" - ") + 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); np.set(QStringLiteral("url"), fileUrl); } #include "mpriscontrolplugin.moc" diff --git a/plugins/mpriscontrol/mpriscontrolplugin.h b/plugins/mpriscontrol/mpriscontrolplugin.h index 6f4c89d4..954357f1 100644 --- a/plugins/mpriscontrol/mpriscontrolplugin.h +++ b/plugins/mpriscontrol/mpriscontrolplugin.h @@ -1,87 +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 . */ #ifndef MPRISCONTROLPLUGIN_H #define MPRISCONTROLPLUGIN_H #include #include -#include #include #include #include - class OrgFreedesktopDBusPropertiesInterface; class OrgMprisMediaPlayer2PlayerInterface; class MprisPlayer { public: MprisPlayer(const QString& serviceName, const QString& dbusObjectPath, const QDBusConnection& busConnection); MprisPlayer() = delete; public: const QString& serviceName() const { return m_serviceName; } OrgFreedesktopDBusPropertiesInterface* propertiesInterface() const { return m_propertiesInterface.data(); } OrgMprisMediaPlayer2PlayerInterface* mediaPlayer2PlayerInterface() const { return m_mediaPlayer2PlayerInterface.data(); } private: QString m_serviceName; QSharedPointer m_propertiesInterface; QSharedPointer m_mediaPlayer2PlayerInterface; }; #define PACKET_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); bool receivePacket(const NetworkPacket& 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& serviceName); void removePlayer(const QString& serviceName); void sendPlayerList(); void mprisPlayerMetadataToNetworkPacket(NetworkPacket& np, const QVariantMap& nowPlayingMap) const; bool sendAlbumArt(const NetworkPacket& np); QHash playerList; int prevVolume; QDBusServiceWatcher* m_watcher; }; #endif diff --git a/plugins/mprisremote/CMakeLists.txt b/plugins/mprisremote/CMakeLists.txt index f65630f7..42b75670 100644 --- a/plugins/mprisremote/CMakeLists.txt +++ b/plugins/mprisremote/CMakeLists.txt @@ -1,6 +1,13 @@ -kdeconnect_add_plugin(kdeconnect_mprisremote JSON kdeconnect_mprisremote.json SOURCES mprisremoteplugin.cpp mprisremoteplayer.cpp mprisremoteplayermediaplayer2.cpp mprisremoteplayermediaplayer2player.cpp) +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER plugin_mprisremote_debug.h + IDENTIFIER KDECONNECT_PLUGIN_MPRISREMOTE CATEGORY_NAME kdeconnect.plugin.mprisremote + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (plugin mprisremote)") + +kdeconnect_add_plugin(kdeconnect_mprisremote JSON kdeconnect_mprisremote.json SOURCES mprisremoteplugin.cpp mprisremoteplayer.cpp mprisremoteplayermediaplayer2.cpp mprisremoteplayermediaplayer2player.cpp ${debug_file_SRCS}) target_link_libraries(kdeconnect_mprisremote kdeconnectcore Qt5::DBus ) diff --git a/plugins/mprisremote/mprisremoteplugin.cpp b/plugins/mprisremote/mprisremoteplugin.cpp index 333d0807..1123c6a7 100644 --- a/plugins/mprisremote/mprisremoteplugin.cpp +++ b/plugins/mprisremote/mprisremoteplugin.cpp @@ -1,224 +1,224 @@ /** * 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 "plugin_mprisremote_debug.h" + K_PLUGIN_CLASS_WITH_JSON(MprisRemotePlugin, "kdeconnect_mprisremote.json") -Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_MPRISREMOTE, "kdeconnect.plugin.mprisremote") MprisRemotePlugin::MprisRemotePlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , m_currentPlayer() , m_players() { } MprisRemotePlugin::~MprisRemotePlugin() { } bool MprisRemotePlugin::receivePacket(const NetworkPacket& np) { if (np.type() != PACKET_TYPE_MPRIS) return false; if (np.has(QStringLiteral("player"))) { const QString player = np.get(QStringLiteral("player")); if(!m_players.contains(player)) { m_players[player] = new MprisRemotePlayer(player, this); } m_players[player]->parseNetworkPacket(np); } if (np.has(QStringLiteral("playerList"))) { QStringList players = np.get(QStringLiteral("playerList")); //Remove players not available any more for (auto iter = m_players.begin(); iter != m_players.end();) { if (!players.contains(iter.key())) { iter.value()->deleteLater(); iter = m_players.erase(iter); } else { ++iter; } } //Add new players for (const QString& player : players) { if (!m_players.contains(player)) { m_players[player] = new MprisRemotePlayer(player, this); requestPlayerStatus(player); } } if (m_players.empty()) { m_currentPlayer = QString(); } else if (!m_players.contains(m_currentPlayer)) { m_currentPlayer = m_players.firstKey(); } } Q_EMIT propertiesChanged(); return true; } long MprisRemotePlugin::position() const { auto player = m_players.value(m_currentPlayer); return player ? player->position() : 0; } QString MprisRemotePlugin::dbusPath() const { return QStringLiteral("/modules/kdeconnect/devices/") + device()->id() + QStringLiteral("/mprisremote"); } void MprisRemotePlugin::requestPlayerStatus(const QString& player) { NetworkPacket np(PACKET_TYPE_MPRIS_REQUEST, { {QStringLiteral("player"), player}, {QStringLiteral("requestNowPlaying"), true}, {QStringLiteral("requestVolume"), true}} ); sendPacket(np); } void MprisRemotePlugin::requestPlayerList() { NetworkPacket np(PACKET_TYPE_MPRIS_REQUEST, {{QStringLiteral("requestPlayerList"), true}}); sendPacket(np); } void MprisRemotePlugin::sendAction(const QString& action) { NetworkPacket np(PACKET_TYPE_MPRIS_REQUEST, { {QStringLiteral("player"), m_currentPlayer}, {QStringLiteral("action"), action} }); sendPacket(np); } void MprisRemotePlugin::seek(int offset) const { NetworkPacket np(PACKET_TYPE_MPRIS_REQUEST, { {QStringLiteral("player"), m_currentPlayer}, {QStringLiteral("Seek"), offset}}); sendPacket(np); } void MprisRemotePlugin::setVolume(int volume) { NetworkPacket np(PACKET_TYPE_MPRIS_REQUEST, { {QStringLiteral("player"), m_currentPlayer}, {QStringLiteral("setVolume"), volume} }); sendPacket(np); } void MprisRemotePlugin::setPosition(int position) { NetworkPacket np(PACKET_TYPE_MPRIS_REQUEST, { {QStringLiteral("player"), m_currentPlayer}, {QStringLiteral("SetPosition"), position} }); sendPacket(np); m_players[m_currentPlayer]->setPosition(position); } void MprisRemotePlugin::setPlayer(const QString& player) { if (m_currentPlayer != player) { m_currentPlayer = player; requestPlayerStatus(player); Q_EMIT propertiesChanged(); } } bool MprisRemotePlugin::isPlaying() const { auto player = m_players.value(m_currentPlayer); return player ? player->playing() : false; } int MprisRemotePlugin::length() const { auto player = m_players.value(m_currentPlayer); return player ? player->length() : 0; } int MprisRemotePlugin::volume() const { auto player = m_players.value(m_currentPlayer); return player ? player->volume() : 0; } QString MprisRemotePlugin::player() const { if (m_currentPlayer.isEmpty()) return QString(); return m_currentPlayer; } QStringList MprisRemotePlugin::playerList() const { return m_players.keys(); } QString MprisRemotePlugin::nowPlaying() const { auto player = m_players.value(m_currentPlayer); return player ? player->nowPlaying() : QString(); } QString MprisRemotePlugin::title() const { auto player = m_players.value(m_currentPlayer); return player ? player->title() : QString(); } QString MprisRemotePlugin::album() const { auto player = m_players.value(m_currentPlayer); return player ? player->album() : QString(); } QString MprisRemotePlugin::artist() const { auto player = m_players.value(m_currentPlayer); return player ? player->artist() : QString(); } bool MprisRemotePlugin::canSeek() const { auto player = m_players.value(m_currentPlayer); return player ? player->canSeek() : false; } #include "mprisremoteplugin.moc" diff --git a/plugins/notifications/CMakeLists.txt b/plugins/notifications/CMakeLists.txt index 74561b91..89a66f08 100644 --- a/plugins/notifications/CMakeLists.txt +++ b/plugins/notifications/CMakeLists.txt @@ -1,17 +1,25 @@ +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER plugin_notification_debug.h + IDENTIFIER KDECONNECT_PLUGIN_NOTIFICATION CATEGORY_NAME kdeconnect.plugin.notification + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (plugin notification)") + set(kdeconnect_notifications_SRCS notification.cpp notificationsplugin.cpp notificationsdbusinterface.cpp sendreplydialog.cpp + ${debug_file_SRCS} ) 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 ) diff --git a/plugins/notifications/notification.cpp b/plugins/notifications/notification.cpp index 9ca66090..6c08a16d 100644 --- a/plugins/notifications/notification.cpp +++ b/plugins/notifications/notification.cpp @@ -1,205 +1,205 @@ /** * 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 "plugin_notification_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include QMap Notification::s_downloadsInProgress; Notification::Notification(const NetworkPacket& np, const Device* device, QObject* parent) : QObject(parent) , m_imagesDir() , m_device(device) { //Make a own directory for each user so no one can see each others icons QString username; #ifdef Q_OS_WIN username = QString::fromLatin1(qgetenv("USERNAME")); #else username = QString::fromLatin1(qgetenv("USER")); #endif m_imagesDir.setPath(QDir::temp().absoluteFilePath(QStringLiteral("kdeconnect_") + username)); m_imagesDir.mkpath(m_imagesDir.absolutePath()); QFile(m_imagesDir.absolutePath()).setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner); m_ready = false; parseNetworkPacket(np); createKNotification(np); connect(m_notification, QOverload::of(&KNotification::activated), this, [this] (unsigned int actionIndex) { // Do nothing for our own reply action if(!m_requestReplyId.isEmpty() && actionIndex == 1) { return; } // Notification action indices start at 1 Q_EMIT actionTriggered(m_internalId, m_actions[actionIndex - 1]); }); } Notification::~Notification() { } void Notification::dismiss() { if (m_dismissable) { Q_EMIT dismissRequested(m_internalId); } } void Notification::show() { m_ready = true; Q_EMIT ready(); if (!m_silent) { m_notification->sendEvent(); } } void Notification::update(const NetworkPacket& np) { parseNetworkPacket(np); createKNotification(np); } void Notification::createKNotification(const NetworkPacket& np) { if (!m_notification) { 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(); if (NotificationServerInfo::instance().supportedHints().testFlag(NotificationServerInfo::X_KDE_DISPLAY_APPNAME)) { m_notification->setTitle(escapedTitle); m_notification->setText(escapedText); m_notification->setHint(QStringLiteral("x-kde-display-appname"), m_appName.toHtmlEscaped()); } else { 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 + QStringLiteral(": ") + escapedText); } } m_notification->setHint(QStringLiteral("x-kde-origin-name"), m_device->name()); if (!m_requestReplyId.isEmpty()) { m_actions.prepend(i18n("Reply")); connect(m_notification, &KNotification::action1Activated, this, &Notification::reply, Qt::UniqueConnection); } m_notification->setActions(m_actions); m_hasIcon = m_hasIcon && !m_payloadHash.isEmpty(); if (!m_hasIcon) { show(); } else { m_iconPath = m_imagesDir.absoluteFilePath(m_payloadHash); loadIcon(np); } } void Notification::loadIcon(const NetworkPacket& np) { m_ready = false; if (QFileInfo::exists(m_iconPath)) { applyIcon(); show(); } else { FileTransferJob* fileTransferJob = s_downloadsInProgress.value(m_iconPath); if (!fileTransferJob) { fileTransferJob = np.createPayloadTransferJob(QUrl::fromLocalFile(m_iconPath)); fileTransferJob->start(); s_downloadsInProgress[m_iconPath] = fileTransferJob; } connect(fileTransferJob, &FileTransferJob::result, this, [this, fileTransferJob]{ s_downloadsInProgress.remove(m_iconPath); if (fileTransferJob->error()) { qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Error in FileTransferJob: " << fileTransferJob->errorString(); } else { applyIcon(); } show(); }); } } void Notification::applyIcon() { QPixmap icon(m_iconPath, "PNG"); m_notification->setPixmap(icon); } void Notification::reply() { Q_EMIT replyRequested(); } void Notification::parseNetworkPacket(const NetworkPacket& 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()); m_actions.clear(); const auto actions = np.get(QStringLiteral("actions")); for (const QJsonValue& value : actions) { m_actions.append(value.toString()); } } diff --git a/plugins/notifications/notification_debug.h b/plugins/notifications/notification_debug.h deleted file mode 100644 index b1366801..00000000 --- a/plugins/notifications/notification_debug.h +++ /dev/null @@ -1,28 +0,0 @@ -/** - * 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 diff --git a/plugins/notifications/notificationsdbusinterface.cpp b/plugins/notifications/notificationsdbusinterface.cpp index 4bcc2f4b..2c693fc1 100644 --- a/plugins/notifications/notificationsdbusinterface.cpp +++ b/plugins/notifications/notificationsdbusinterface.cpp @@ -1,192 +1,192 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "notificationsdbusinterface.h" -#include "notification_debug.h" +#include "plugin_notification_debug.h" #include "notification.h" #include #include #include #include "notificationsplugin.h" #include "sendreplydialog.h" //In older Qt released, qAsConst isnt available #include "qtcompat_p.h" NotificationsDbusInterface::NotificationsDbusInterface(KdeConnectPlugin* plugin) : QDBusAbstractAdaptor(const_cast(plugin->device())) , m_device(plugin->device()) , m_plugin(plugin) , m_lastId(0) { } NotificationsDbusInterface::~NotificationsDbusInterface() { qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Destroying NotificationsDbusInterface"; } void NotificationsDbusInterface::clearNotifications() { qDeleteAll(m_notifications); m_notifications.clear(); Q_EMIT allNotificationsRemoved(); } QStringList NotificationsDbusInterface::activeNotifications() { return m_notifications.keys(); } void NotificationsDbusInterface::notificationReady() { Notification* noti = static_cast(sender()); disconnect(noti, &Notification::ready, this, &NotificationsDbusInterface::notificationReady); addNotification(noti); } void NotificationsDbusInterface::processPacket(const NetworkPacket& np) { if (np.get(QStringLiteral("isCancel"))) { QString id = np.get(QStringLiteral("id")); // cut off kdeconnect-android's prefix if there: if (id.startsWith(QLatin1String("org.kde.kdeconnect_tp::"))) id = id.mid(id.indexOf(QLatin1String("::")) + 2); removeNotification(id); return; } QString id = np.get(QStringLiteral("id")); Notification* noti = nullptr; if (!m_internalIdToPublicId.contains(id)) { noti = new Notification(np, m_plugin->device(), this); if (noti->isReady()) { addNotification(noti); } else { connect(noti, &Notification::ready, this, &NotificationsDbusInterface::notificationReady); } } else { QString pubId = m_internalIdToPublicId.value(id); noti = m_notifications.value(pubId); noti->update(np); } } void NotificationsDbusInterface::addNotification(Notification* noti) { const QString& internalId = noti->internalId(); if (m_internalIdToPublicId.contains(internalId)) { removeNotification(internalId); } //qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "addNotification" << internalId; connect(noti, &Notification::dismissRequested, this, &NotificationsDbusInterface::dismissRequested); connect(noti, &Notification::replyRequested, this, [this,noti]{ replyRequested(noti); }); connect(noti, &Notification::actionTriggered, this, &NotificationsDbusInterface::sendAction); const QString& publicId = newId(); m_notifications[publicId] = noti; m_internalIdToPublicId[internalId] = publicId; DBusHelper::sessionBus().registerObject(m_device->dbusPath() + QStringLiteral("/notifications/") + publicId, noti, QDBusConnection::ExportScriptableContents); Q_EMIT notificationPosted(publicId); } void NotificationsDbusInterface::removeNotification(const QString& internalId) { //qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "removeNotification" << internalId; if (!m_internalIdToPublicId.contains(internalId)) { qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Not found noti by internal Id: " << internalId; return; } QString publicId = m_internalIdToPublicId.take(internalId); Notification* noti = m_notifications.take(publicId); if (!noti) { qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Not found noti by public Id: " << publicId; return; } //Deleting the notification will unregister it automatically //DBusHelper::sessionBus().unregisterObject(mDevice->dbusPath()+"/notifications/"+publicId); noti->deleteLater(); Q_EMIT notificationRemoved(publicId); } void NotificationsDbusInterface::dismissRequested(const QString& internalId) { NetworkPacket np(PACKET_TYPE_NOTIFICATION_REQUEST); np.set(QStringLiteral("cancel"), internalId); m_plugin->sendPacket(np); //Workaround: we erase notifications without waiting a response from the //phone because we won't receive a response if we are out of sync and this //notification no longer exists. Ideally, each time we reach the phone //after some time disconnected we should re-sync all the notifications. removeNotification(internalId); } void NotificationsDbusInterface::replyRequested(Notification* noti) { QString replyId = noti->replyId(); QString appName = noti->appName(); QString originalMessage = noti->ticker(); SendReplyDialog* dialog = new SendReplyDialog(originalMessage, replyId, appName); connect(dialog, &SendReplyDialog::sendReply, this, &NotificationsDbusInterface::sendReply); dialog->show(); dialog->raise(); } void NotificationsDbusInterface::sendReply(const QString& replyId, const QString& message) { NetworkPacket np(PACKET_TYPE_NOTIFICATION_REPLY); np.set(QStringLiteral("requestReplyId"), replyId); np.set(QStringLiteral("message"), message); m_plugin->sendPacket(np); } void NotificationsDbusInterface::sendAction(const QString& key, const QString& action) { NetworkPacket np(PACKET_TYPE_NOTIFICATION_ACTION); np.set(QStringLiteral("key"), key); np.set(QStringLiteral("action"), action); m_plugin->sendPacket(np); } QString NotificationsDbusInterface::newId() { return QString::number(++m_lastId); } diff --git a/plugins/notifications/notificationsplugin.cpp b/plugins/notifications/notificationsplugin.cpp index 115f341f..f646ad34 100644 --- a/plugins/notifications/notificationsplugin.cpp +++ b/plugins/notifications/notificationsplugin.cpp @@ -1,64 +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 . */ #include "notificationsplugin.h" #include "notificationsdbusinterface.h" -#include "notification_debug.h" +#include "plugin_notification_debug.h" #include K_PLUGIN_CLASS_WITH_JSON(NotificationsPlugin, "kdeconnect_notifications.json") -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() { NetworkPacket np(PACKET_TYPE_NOTIFICATION_REQUEST, {{QStringLiteral("request"), true}}); sendPacket(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::receivePacket(const NetworkPacket& np) { if (np.get(QStringLiteral("request"))) return false; notificationsDbusInterface->processPacket(np); return true; } #include "notificationsplugin.moc" diff --git a/plugins/pausemusic/CMakeLists.txt b/plugins/pausemusic/CMakeLists.txt index d2b99d02..af857e88 100644 --- a/plugins/pausemusic/CMakeLists.txt +++ b/plugins/pausemusic/CMakeLists.txt @@ -1,41 +1,48 @@ if(WIN32) set(kdeconnect_pausemusic_SRCS pausemusicplugin-win.cpp ) else() set(kdeconnect_pausemusic_SRCS pausemusicplugin.cpp ) endif() +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER plugin_pausemusic_debug.h + IDENTIFIER KDECONNECT_PLUGIN_PAUSEMUSIC CATEGORY_NAME kdeconnect.plugin.pausemusic + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (plugin pausemusic)") + qt5_add_dbus_interface(kdeconnect_pausemusic_SRCS org.mpris.MediaPlayer2.Player.xml mprisplayer) -kdeconnect_add_plugin(kdeconnect_pausemusic JSON kdeconnect_pausemusic.json SOURCES ${kdeconnect_pausemusic_SRCS}) +kdeconnect_add_plugin(kdeconnect_pausemusic JSON kdeconnect_pausemusic.json SOURCES ${kdeconnect_pausemusic_SRCS} ${debug_file_SRCS}) target_link_libraries(kdeconnect_pausemusic kdeconnectcore Qt5::Core Qt5::DBus ) if (NOT WIN32) target_link_libraries(kdeconnect_pausemusic KF5::PulseAudioQt ) endif() ####################################### # 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/pausemusicplugin-win.cpp b/plugins/pausemusic/pausemusicplugin-win.cpp index 2bc41580..ed90fd68 100644 --- a/plugins/pausemusic/pausemusicplugin-win.cpp +++ b/plugins/pausemusic/pausemusicplugin-win.cpp @@ -1,107 +1,106 @@ /** * Copyright 2013 Albert Vaca * Copyright 2019 Piyush Aggarwal * * 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-win.h" #include +#include "plugin_pausemusic_debug.h" K_PLUGIN_CLASS_WITH_JSON(PauseMusicPlugin, "kdeconnect_pausemusic.json") -Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_PAUSEMUSIC, "kdeconnect.plugin.pausemusic") - PauseMusicPlugin::PauseMusicPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) { CoInitialize(NULL); deviceEnumerator = NULL; CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (LPVOID *)&deviceEnumerator); defaultDevice = NULL; g_guidMyContext = GUID_NULL; deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDevice); deviceEnumerator->Release(); deviceEnumerator = NULL; endpointVolume = NULL; defaultDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (LPVOID *)&endpointVolume); defaultDevice->Release(); defaultDevice = NULL; CoCreateGuid(&g_guidMyContext); } PauseMusicPlugin::~PauseMusicPlugin() { endpointVolume->Release(); CoUninitialize(); } bool PauseMusicPlugin::receivePacket(const NetworkPacket& np) { bool pauseOnlyWhenTalking = config()->get(QStringLiteral("conditionTalking"), false); if (pauseOnlyWhenTalking) { if (np.get(QStringLiteral("event")) != QLatin1String("talking")) { return true; } } else { if (np.get(QStringLiteral("event")) != QLatin1String("ringing") && np.get(QStringLiteral("event")) != QLatin1String("talking")) { return true; } } bool pauseConditionFulfilled = !np.get(QStringLiteral("isCancel")); bool pause = config()->get(QStringLiteral("actionPause"), false); bool mute = config()->get(QStringLiteral("actionMute"), true); const bool autoResume = config()->get(QStringLiteral("actionResume"), true); if (pauseConditionFulfilled) { if (mute) { qCDebug(KDECONNECT_PLUGIN_PAUSEMUSIC) << "Muting music"; endpointVolume->SetMute(TRUE, &g_guidMyContext); } if (pause) { // TODO PAUSING } } else { if (mute) { qCDebug(KDECONNECT_PLUGIN_PAUSEMUSIC) << "Unmuting system volume"; if (autoResume) { endpointVolume->SetMute(FALSE, &g_guidMyContext); } } if (pause) { // TODO UNPAUSING } } return true; } #include "pausemusicplugin-win.moc" diff --git a/plugins/pausemusic/pausemusicplugin-win.h b/plugins/pausemusic/pausemusicplugin-win.h index 51b64723..95ea4b63 100644 --- a/plugins/pausemusic/pausemusicplugin-win.h +++ b/plugins/pausemusic/pausemusicplugin-win.h @@ -1,58 +1,54 @@ /** * Copyright 2013 Albert Vaca * Copyright 2019 Piyush Aggarwal * * 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 PAUSEMUSICPLUGINWIN_H #define PAUSEMUSICPLUGINWIN_H #include #include #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); ~PauseMusicPlugin(); bool receivePacket(const NetworkPacket& np) override; void connected() override { } private: IMMDeviceEnumerator *deviceEnumerator; IMMDevice *defaultDevice; GUID g_guidMyContext; IAudioEndpointVolume *endpointVolume; }; #endif // PAUSEMUSICPLUGINWIN_H diff --git a/plugins/pausemusic/pausemusicplugin.cpp b/plugins/pausemusic/pausemusicplugin.cpp index 1e8d6ef9..9f14cd59 100644 --- a/plugins/pausemusic/pausemusicplugin.cpp +++ b/plugins/pausemusic/pausemusicplugin.cpp @@ -1,130 +1,129 @@ /** * 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 "mprisplayer.h" //In older Qt released, qAsConst isnt available #include "qtcompat_p.h" +#include "plugin_pausemusic_debug.h" K_PLUGIN_CLASS_WITH_JSON(PauseMusicPlugin, "kdeconnect_pausemusic.json") -Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_PAUSEMUSIC, "kdeconnect.plugin.pausemusic") - PauseMusicPlugin::PauseMusicPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , mutedSinks() {} bool PauseMusicPlugin::receivePacket(const NetworkPacket& np) { bool pauseOnlyWhenTalking = config()->get(QStringLiteral("conditionTalking"), false); if (pauseOnlyWhenTalking) { if (np.get(QStringLiteral("event")) != QLatin1String("talking")) { return true; } } else { //Pause as soon as it rings if (np.get(QStringLiteral("event")) != QLatin1String("ringing") && np.get(QStringLiteral("event")) != QLatin1String("talking")) { return true; } } bool pauseConditionFulfilled = !np.get(QStringLiteral("isCancel")); bool pause = config()->get(QStringLiteral("actionPause"), true); bool mute = config()->get(QStringLiteral("actionMute"), false); const bool autoResume = config()->get(QStringLiteral("actionResume"), true); if (pauseConditionFulfilled) { if (mute) { qCDebug(KDECONNECT_PLUGIN_PAUSEMUSIC) << "Muting system volume"; const auto sinks = PulseAudioQt::Context::instance()->sinks(); for (const auto sink : sinks) { if (!sink->isMuted()) { sink->setMuted(true); mutedSinks.insert(sink->name()); } } } if (pause) { //Search for interfaces currently playing const QStringList interfaces = DBusHelper::sessionBus().interface()->registeredServiceNames().value(); for (const QString& iface : interfaces) { if (iface.startsWith(QLatin1String("org.mpris.MediaPlayer2"))) { OrgMprisMediaPlayer2PlayerInterface mprisInterface(iface, QStringLiteral("/org/mpris/MediaPlayer2"), DBusHelper::sessionBus()); QString status = mprisInterface.playbackStatus(); if (status == QLatin1String("Playing")) { if (!pausedSources.contains(iface)) { pausedSources.insert(iface); if (mprisInterface.canPause()) { mprisInterface.Pause(); } else { mprisInterface.Stop(); } } } } } } } else { if (mute) { qCDebug(KDECONNECT_PLUGIN_PAUSEMUSIC) << "Unmuting system volume"; if (autoResume) { const auto sinks = PulseAudioQt::Context::instance()->sinks(); for (const auto sink : sinks) { if (mutedSinks.contains(sink->name())) { sink->setMuted(false); } } } mutedSinks.clear(); } if (pause && !pausedSources.empty()) { if (autoResume) { for (const QString& iface : qAsConst(pausedSources)) { OrgMprisMediaPlayer2PlayerInterface mprisInterface(iface, QStringLiteral("/org/mpris/MediaPlayer2"), DBusHelper::sessionBus()); mprisInterface.Play(); } } pausedSources.clear(); } } return true; } #include "pausemusicplugin.moc" diff --git a/plugins/pausemusic/pausemusicplugin.h b/plugins/pausemusic/pausemusicplugin.h index 7457b600..43a12fdd 100644 --- a/plugins/pausemusic/pausemusicplugin.h +++ b/plugins/pausemusic/pausemusicplugin.h @@ -1,51 +1,47 @@ /** * 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); bool receivePacket(const NetworkPacket& np) override; void connected() override { } private: QSet pausedSources; QSet mutedSinks; }; #endif diff --git a/plugins/photo/CMakeLists.txt b/plugins/photo/CMakeLists.txt index 740dffc3..21c7910d 100644 --- a/plugins/photo/CMakeLists.txt +++ b/plugins/photo/CMakeLists.txt @@ -1,10 +1,18 @@ +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER plugin_photo_debug.h + IDENTIFIER KDECONNECT_PLUGIN_PHOTO CATEGORY_NAME kdeconnect.plugin.photo + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (plugin photo)") + set(kdeconnect_photo_SRCS photoplugin.cpp + ${debug_file_SRCS} ) kdeconnect_add_plugin(kdeconnect_photo JSON kdeconnect_photo.json SOURCES ${kdeconnect_photo_SRCS}) target_link_libraries(kdeconnect_photo kdeconnectcore Qt5::DBus ) diff --git a/plugins/photo/photoplugin.cpp b/plugins/photo/photoplugin.cpp index 8fb0302c..37333b84 100644 --- a/plugins/photo/photoplugin.cpp +++ b/plugins/photo/photoplugin.cpp @@ -1,76 +1,74 @@ /** * Copyright 2019 Nicolas Fella * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "photoplugin.h" #include #include -#include #include +#include "plugin_photo_debug.h" K_PLUGIN_CLASS_WITH_JSON(PhotoPlugin, "kdeconnect_photo.json") -Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_PHOTO, "kdeconnect.plugin.photo") - PhotoPlugin::PhotoPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) { } PhotoPlugin::~PhotoPlugin() { } bool PhotoPlugin::receivePacket(const NetworkPacket& np) { if (np.get(QStringLiteral("cancel"))) { requestedFiles.takeFirst(); } if (requestedFiles.isEmpty() || !np.hasPayload()) { return true; } const QString& fileName = requestedFiles.takeFirst(); FileTransferJob* job = np.createPayloadTransferJob(QUrl::fromLocalFile(fileName)); connect(job, &FileTransferJob::result, this, [this, fileName] { Q_EMIT photoReceived(fileName); }); job->start(); return true; } void PhotoPlugin::requestPhoto(const QString& fileName) { requestedFiles.append(fileName); NetworkPacket np(PACKET_TYPE_PHOTO_REQUEST); sendPacket(np); } QString PhotoPlugin::dbusPath() const { return QStringLiteral("/modules/kdeconnect/devices/") + device()->id() + QStringLiteral("/photo"); } #include "photoplugin.moc" diff --git a/plugins/ping/CMakeLists.txt b/plugins/ping/CMakeLists.txt index 1ea45d16..56fb60e8 100644 --- a/plugins/ping/CMakeLists.txt +++ b/plugins/ping/CMakeLists.txt @@ -1,11 +1,19 @@ +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER plugin_ping_debug.h + IDENTIFIER KDECONNECT_PLUGIN_PING CATEGORY_NAME kdeconnect.plugin.ping + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (plugin ping)") + set(kdeconnect_ping_SRCS pingplugin.cpp + ${debug_file_SRCS} ) kdeconnect_add_plugin(kdeconnect_ping JSON kdeconnect_ping.json SOURCES ${kdeconnect_ping_SRCS}) target_link_libraries(kdeconnect_ping kdeconnectcore Qt5::DBus KF5::I18n ) diff --git a/plugins/ping/pingplugin.cpp b/plugins/ping/pingplugin.cpp index a324acc5..341c7c41 100644 --- a/plugins/ping/pingplugin.cpp +++ b/plugins/ping/pingplugin.cpp @@ -1,78 +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 "pingplugin.h" #include #include #include #include -#include #include #include -K_PLUGIN_CLASS_WITH_JSON(PingPlugin, "kdeconnect_ping.json") +#include "plugin_ping_debug.h" -Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_PING, "kdeconnect.plugin.ping") +K_PLUGIN_CLASS_WITH_JSON(PingPlugin, "kdeconnect_ping.json") 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::receivePacket(const NetworkPacket& np) { Daemon::instance()->sendSimpleNotification(QStringLiteral("pingReceived"), device()->name(), np.get(QStringLiteral("message"),i18n("Ping!")), QStringLiteral("dialog-ok")); return true; } void PingPlugin::sendPing() { NetworkPacket np(PACKET_TYPE_PING); bool success = sendPacket(np); qCDebug(KDECONNECT_PLUGIN_PING) << "sendPing:" << success; } void PingPlugin::sendPing(const QString& customMessage) { NetworkPacket np(PACKET_TYPE_PING); if (!customMessage.isEmpty()) { np.set(QStringLiteral("message"), customMessage); } bool success = sendPacket(np); qCDebug(KDECONNECT_PLUGIN_PING) << "sendPing:" << success; } QString PingPlugin::dbusPath() const { return QStringLiteral("/modules/kdeconnect/devices/") + device()->id() + QStringLiteral("/ping"); } #include "pingplugin.moc" diff --git a/plugins/presenter/CMakeLists.txt b/plugins/presenter/CMakeLists.txt index 6fea7f67..42c2f0bd 100644 --- a/plugins/presenter/CMakeLists.txt +++ b/plugins/presenter/CMakeLists.txt @@ -1,9 +1,16 @@ qt5_add_resources(presenter_SRCS assets.qrc) -kdeconnect_add_plugin(kdeconnect_presenter JSON kdeconnect_presenter.json SOURCES presenterplugin.cpp ${presenter_SRCS}) +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER plugin_presenter_debug.h + IDENTIFIER KDECONNECT_PLUGIN_PRESENT CATEGORY_NAME kdeconnect.plugin.presenter + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (plugin presenter)") + +kdeconnect_add_plugin(kdeconnect_presenter JSON kdeconnect_presenter.json SOURCES presenterplugin.cpp ${presenter_SRCS} ${debug_file_SRCS}) target_link_libraries(kdeconnect_presenter kdeconnectcore Qt5::DBus Qt5::Quick KF5::I18n ) diff --git a/plugins/presenter/presenterplugin.cpp b/plugins/presenter/presenterplugin.cpp index 5f6c53f0..766e5d4f 100644 --- a/plugins/presenter/presenterplugin.cpp +++ b/plugins/presenter/presenterplugin.cpp @@ -1,113 +1,111 @@ /** * Copyright 2019 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 "presenterplugin.h" #include #include #include #include -#include #include #include #include #include #include #include #include +#include "plugin_presenter_debug.h" K_PLUGIN_CLASS_WITH_JSON(PresenterPlugin, "kdeconnect_presenter.json") -Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_PRESENT, "kdeconnect.plugin.presenter") - class PresenterView : public QQuickView { public: PresenterView() { Qt::WindowFlags windowFlags = Qt::WindowFlags(Qt::WA_TranslucentBackground | Qt::WindowDoesNotAcceptFocus | Qt::WindowFullScreen | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::Tool); #ifdef Q_OS_WIN windowFlags |= Qt::WindowTransparentForInput; #endif setFlags(windowFlags); setClearBeforeRendering(true); setColor(QColor(Qt::transparent)); setResizeMode(QQuickView::SizeViewToRootObject); setSource(QUrl(QStringLiteral("qrc:/presenter/Presenter.qml"))); const auto ourErrors = errors(); for (const auto &error : ourErrors) { qCWarning(KDECONNECT_PLUGIN_PRESENT) << "error" << error.description() << error.url() << error.line() << error.column(); } } }; PresenterPlugin::PresenterPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , m_view(nullptr) , m_timer(new QTimer(this)) { m_timer->setInterval(500); m_timer->setSingleShot(true); } PresenterPlugin::~PresenterPlugin() { } bool PresenterPlugin::receivePacket(const NetworkPacket& np) { if (np.get(QStringLiteral("stop"), false)) { delete m_view; m_view = nullptr; return true; } if (!m_view) { m_view = new PresenterView; m_xPos = 0.5f; m_yPos = 0.5f; m_view->showFullScreen(); connect(this, &QObject::destroyed, m_view, &QObject::deleteLater); connect(m_timer, &QTimer::timeout, m_view, &QObject::deleteLater); } QSize screenSize = m_view->screen()->size(); float ratio = float(screenSize.width())/screenSize.height(); m_xPos += np.get(QStringLiteral("dx")); m_yPos += np.get(QStringLiteral("dy")) * ratio; m_xPos = qBound(0.f, m_xPos, 1.f); m_yPos = qBound(0.f, m_yPos, 1.f); m_timer->start(); QQuickItem* object = m_view->rootObject(); object->setProperty("xPos", m_xPos); object->setProperty("yPos", m_yPos); return true; } #include "presenterplugin.moc" diff --git a/plugins/remotecommands/CMakeLists.txt b/plugins/remotecommands/CMakeLists.txt index 179400a8..4645f16a 100644 --- a/plugins/remotecommands/CMakeLists.txt +++ b/plugins/remotecommands/CMakeLists.txt @@ -1,8 +1,16 @@ +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER plugin_remotecommands_debug.h + IDENTIFIER KDECONNECT_PLUGIN_REMOTECOMMANDS CATEGORY_NAME kdeconnect.plugin.remotecommands + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (plugin remotecommands)") + kdeconnect_add_plugin(kdeconnect_remotecommands JSON kdeconnect_remotecommands.json - SOURCES remotecommandsplugin.cpp + SOURCES remotecommandsplugin.cpp ${debug_file_SRCS} ) + target_link_libraries(kdeconnect_remotecommands kdeconnectcore Qt5::DBus ) diff --git a/plugins/remotecommands/remotecommandsplugin.cpp b/plugins/remotecommands/remotecommandsplugin.cpp index 4b103c1c..64a2f140 100644 --- a/plugins/remotecommands/remotecommandsplugin.cpp +++ b/plugins/remotecommands/remotecommandsplugin.cpp @@ -1,87 +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 "plugin_remotecommands_debug.h" + #define PACKET_TYPE_RUNCOMMAND_REQUEST QLatin1String("kdeconnect.runcommand.request") K_PLUGIN_CLASS_WITH_JSON(RemoteCommandsPlugin, "kdeconnect_remotecommands.json") -Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_REMOTECOMMANDS, "kdeconnect.plugin.remotecommands") - RemoteCommandsPlugin::RemoteCommandsPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , m_commands("{}") , m_canAddCommand(false) { } RemoteCommandsPlugin::~RemoteCommandsPlugin() = default; bool RemoteCommandsPlugin::receivePacket(const NetworkPacket& np) { if (np.has(QStringLiteral("commandList"))) { m_canAddCommand = np.get(QStringLiteral("canAddCommand")); setCommands(np.get(QStringLiteral("commandList"))); return true; } return false; } void RemoteCommandsPlugin::connected() { NetworkPacket np(PACKET_TYPE_RUNCOMMAND_REQUEST, {{QStringLiteral("requestCommandList"), true}}); sendPacket(np); } QString RemoteCommandsPlugin::dbusPath() const { return QStringLiteral("/modules/kdeconnect/devices/") + device()->id() + QStringLiteral("/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) { NetworkPacket np(PACKET_TYPE_RUNCOMMAND_REQUEST, {{QStringLiteral("key"), key }}); sendPacket(np); } void RemoteCommandsPlugin::editCommands() { NetworkPacket np(PACKET_TYPE_RUNCOMMAND_REQUEST, {{QStringLiteral("setup"), true }}); sendPacket(np); } #include "remotecommandsplugin.moc" diff --git a/plugins/remotecontrol/CMakeLists.txt b/plugins/remotecontrol/CMakeLists.txt index c33d932d..5496dc54 100644 --- a/plugins/remotecontrol/CMakeLists.txt +++ b/plugins/remotecontrol/CMakeLists.txt @@ -1,7 +1,14 @@ -kdeconnect_add_plugin(kdeconnect_remotecontrol JSON kdeconnect_remotecontrol.json SOURCES remotecontrolplugin.cpp) +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER plugin_remotecontrol_debug.h + IDENTIFIER KDECONNECT_PLUGIN_REMOTECONTROL CATEGORY_NAME kdeconnect.plugin.remotecontrol + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (plugin remotecontrol)") + +kdeconnect_add_plugin(kdeconnect_remotecontrol JSON kdeconnect_remotecontrol.json SOURCES remotecontrolplugin.cpp ${debug_file_SRCS}) target_link_libraries(kdeconnect_remotecontrol kdeconnectcore Qt5::DBus KF5::I18n ) diff --git a/plugins/remotecontrol/remotecontrolplugin.cpp b/plugins/remotecontrol/remotecontrolplugin.cpp index dd6eb668..64c39ea2 100644 --- a/plugins/remotecontrol/remotecontrolplugin.cpp +++ b/plugins/remotecontrol/remotecontrolplugin.cpp @@ -1,65 +1,63 @@ /** * 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 +#include "plugin_remotecontrol_debug.h" K_PLUGIN_CLASS_WITH_JSON(RemoteControlPlugin, "kdeconnect_remotecontrol.json") -Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_REMOTECONTROL, "kdeconnect.plugin.remotecontrol") - RemoteControlPlugin::RemoteControlPlugin(QObject* parent, const QVariantList &args) : KdeConnectPlugin(parent, args) { } RemoteControlPlugin::~RemoteControlPlugin() {} void RemoteControlPlugin::moveCursor(const QPoint &p) { NetworkPacket np(PACKET_TYPE_MOUSEPAD_REQUEST, { {QStringLiteral("dx"), p.x()}, {QStringLiteral("dy"), p.y()} }); sendPacket(np); } void RemoteControlPlugin::sendCommand(const QString &name, bool val) { NetworkPacket np(PACKET_TYPE_MOUSEPAD_REQUEST, {{name, val}}); sendPacket(np); } QString RemoteControlPlugin::dbusPath() const { return QStringLiteral("/modules/kdeconnect/devices/") + device()->id() + QStringLiteral("/remotecontrol"); } #include "remotecontrolplugin.moc" diff --git a/plugins/remotekeyboard/CMakeLists.txt b/plugins/remotekeyboard/CMakeLists.txt index 43a4cd06..02137749 100644 --- a/plugins/remotekeyboard/CMakeLists.txt +++ b/plugins/remotekeyboard/CMakeLists.txt @@ -1,7 +1,14 @@ +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER plugin_remotekeyboard_debug.h + IDENTIFIER KDECONNECT_PLUGIN_REMOTEKEYBOARD CATEGORY_NAME kdeconnect.plugin.remotekeyboard + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (plugin remotekeyboard)") + kdeconnect_add_plugin(kdeconnect_remotekeyboard JSON kdeconnect_remotekeyboard.json - SOURCES remotekeyboardplugin.cpp) + SOURCES remotekeyboardplugin.cpp ${debug_file_SRCS}) target_link_libraries(kdeconnect_remotekeyboard kdeconnectcore Qt5::DBus ) diff --git a/plugins/remotekeyboard/remotekeyboardplugin.cpp b/plugins/remotekeyboard/remotekeyboardplugin.cpp index 8c9c087e..8c70502f 100644 --- a/plugins/remotekeyboard/remotekeyboardplugin.cpp +++ b/plugins/remotekeyboard/remotekeyboardplugin.cpp @@ -1,148 +1,147 @@ /** * 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 "plugin_remotekeyboard_debug.h" K_PLUGIN_CLASS_WITH_JSON(RemoteKeyboardPlugin, "kdeconnect_remotekeyboard.json") -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::receivePacket(const NetworkPacket& np) { if (np.type() == PACKET_TYPE_MOUSEPAD_ECHO) { if (!np.has(QStringLiteral("isAck")) || !np.has(QStringLiteral("key"))) { qCWarning(KDECONNECT_PLUGIN_REMOTEKEYBOARD) << "Invalid packet of type" << PACKET_TYPE_MOUSEPAD_ECHO; return false; } // qCWarning(KDECONNECT_PLUGIN_REMOTEKEYBOARD) << "Received keypress" << np; Q_EMIT keyPressReceived(np.get(QStringLiteral("key")), np.get(QStringLiteral("specialKey"), 0), np.get(QStringLiteral("shift"), false), np.get(QStringLiteral("ctrl"), false), np.get(QStringLiteral("alt"), 0)); return true; } else if (np.type() == PACKET_TYPE_MOUSEPAD_KEYBOARDSTATE) { // qCWarning(KDECONNECT_PLUGIN_REMOTEKEYBOARD) << "Received keyboardstate" << np; if (m_remoteState != np.get(QStringLiteral("state"))) { m_remoteState = np.get(QStringLiteral("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 { NetworkPacket np(PACKET_TYPE_MOUSEPAD_REQUEST, { {QStringLiteral("key"), key}, {QStringLiteral("specialKey"), specialKey}, {QStringLiteral("shift"), shift}, {QStringLiteral("ctrl"), ctrl}, {QStringLiteral("alt"), alt}, {QStringLiteral("sendAck"), sendAck} }); sendPacket(np); } void RemoteKeyboardPlugin::sendQKeyEvent(const QVariantMap& keyEvent, bool sendAck) const { if (!keyEvent.contains(QStringLiteral("key"))) return; int k = translateQtKey(keyEvent.value(QStringLiteral("key")).toInt()); int modifiers = keyEvent.value(QStringLiteral("modifiers")).toInt(); sendKeyPress(keyEvent.value(QStringLiteral("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 QStringLiteral("/modules/kdeconnect/devices/") + device()->id() + QStringLiteral("/remotekeyboard"); } #include "remotekeyboardplugin.moc" diff --git a/plugins/remotekeyboard/remotekeyboardplugin.h b/plugins/remotekeyboard/remotekeyboardplugin.h index d6cc06cd..54c46e24 100644 --- a/plugins/remotekeyboard/remotekeyboardplugin.h +++ b/plugins/remotekeyboard/remotekeyboardplugin.h @@ -1,73 +1,70 @@ /** * 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 PACKET_TYPE_MOUSEPAD_REQUEST QLatin1String("kdeconnect.mousepad.request") #define PACKET_TYPE_MOUSEPAD_ECHO QLatin1String("kdeconnect.mousepad.echo") #define PACKET_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 receivePacket(const NetworkPacket& 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); Q_SCRIPTABLE void remoteStateChanged(bool state); }; #endif diff --git a/plugins/remotesystemvolume/CMakeLists.txt b/plugins/remotesystemvolume/CMakeLists.txt index a8eb04cf..ba12e120 100644 --- a/plugins/remotesystemvolume/CMakeLists.txt +++ b/plugins/remotesystemvolume/CMakeLists.txt @@ -1,11 +1,19 @@ +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER plugin_remotesystemvolume_debug.h + IDENTIFIER KDECONNECT_PLUGIN_REMOTESYSTEMVOLUME CATEGORY_NAME kdeconnect.plugin.remotesystemvolume + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (plugin remotesystemvolume)") + set(kdeconnect_remotesystemvolume_SRCS remotesystemvolumeplugin.cpp + ${debug_file_SRCS} ) kdeconnect_add_plugin(kdeconnect_remotesystemvolume JSON kdeconnect_remotesystemvolume.json SOURCES ${kdeconnect_remotesystemvolume_SRCS}) target_link_libraries(kdeconnect_remotesystemvolume kdeconnectcore Qt5::DBus KF5::I18n ) diff --git a/plugins/remotesystemvolume/remotesystemvolumeplugin.cpp b/plugins/remotesystemvolume/remotesystemvolumeplugin.cpp index 9ff33206..bcd4cc88 100644 --- a/plugins/remotesystemvolume/remotesystemvolumeplugin.cpp +++ b/plugins/remotesystemvolume/remotesystemvolumeplugin.cpp @@ -1,105 +1,104 @@ /** * Copyright 2018 Nicolas Fella * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "remotesystemvolumeplugin.h" #include #include #include #include -#include #include #include #include #include -K_PLUGIN_CLASS_WITH_JSON(RemoteSystemVolumePlugin, "kdeconnect_remotesystemvolume.json") +#include "plugin_remotesystemvolume_debug.h" -Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_REMOTESYSTEMVOLUME, "kdeconnect.plugin.remotesystemvolume") +K_PLUGIN_CLASS_WITH_JSON(RemoteSystemVolumePlugin, "kdeconnect_remotesystemvolume.json") RemoteSystemVolumePlugin::RemoteSystemVolumePlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) { } RemoteSystemVolumePlugin::~RemoteSystemVolumePlugin() { } bool RemoteSystemVolumePlugin::receivePacket(const NetworkPacket& np) { if (np.has(QStringLiteral("sinkList"))) { QJsonDocument document(np.get(QStringLiteral("sinkList"))); m_sinks = document.toJson(); Q_EMIT sinksChanged(); } else { QString name = np.get(QStringLiteral("name")); if (np.has(QStringLiteral("volume"))) { Q_EMIT volumeChanged(name, np.get(QStringLiteral("volume"))); } if (np.has(QStringLiteral("muted"))) { Q_EMIT mutedChanged(name, np.get(QStringLiteral("muted"))); } } return true; } void RemoteSystemVolumePlugin::sendVolume(const QString& name, int volume) { NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME_REQUEST); np.set(QStringLiteral("name"), name); np.set(QStringLiteral("volume"), volume); sendPacket(np); } void RemoteSystemVolumePlugin::sendMuted(const QString& name, bool muted) { NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME_REQUEST); np.set(QStringLiteral("name"), name); np.set(QStringLiteral("muted"), muted); sendPacket(np); } void RemoteSystemVolumePlugin::connected() { NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME_REQUEST); np.set(QStringLiteral("requestSinks"), true); sendPacket(np); } QByteArray RemoteSystemVolumePlugin::sinks() { return m_sinks; } QString RemoteSystemVolumePlugin::dbusPath() const { return QStringLiteral("/modules/kdeconnect/devices/") + device()->id() + QStringLiteral("/remotesystemvolume"); } #include "remotesystemvolumeplugin.moc" diff --git a/plugins/runcommand/CMakeLists.txt b/plugins/runcommand/CMakeLists.txt index b2615e9f..8f492c82 100644 --- a/plugins/runcommand/CMakeLists.txt +++ b/plugins/runcommand/CMakeLists.txt @@ -1,28 +1,36 @@ +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER plugin_runcommand_debug.h + IDENTIFIER KDECONNECT_PLUGIN_RUNCOMMAND CATEGORY_NAME kdeconnect.plugin.runcommand + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (plugin runcommand)") + set(kdeconnect_runcommand_SRCS runcommandplugin.cpp + ${debug_file_SRCS} ) kdeconnect_add_plugin(kdeconnect_runcommand JSON kdeconnect_runcommand.json SOURCES ${kdeconnect_runcommand_SRCS}) target_link_libraries(kdeconnect_runcommand kdeconnectcore Qt5::DBus KF5::I18n) #---------------------- if(NOT SAILFISHOS) set( kdeconnect_runcommand_config_SRCS runcommand_config.cpp ) add_library(kdeconnect_runcommand_config MODULE ${kdeconnect_runcommand_config_SRCS} ) target_link_libraries( kdeconnect_runcommand_config kdeconnectcore 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} ) endif() diff --git a/plugins/runcommand/runcommandplugin.cpp b/plugins/runcommand/runcommandplugin.cpp index a8e3548a..81f91d32 100644 --- a/plugins/runcommand/runcommandplugin.cpp +++ b/plugins/runcommand/runcommandplugin.cpp @@ -1,116 +1,115 @@ /** * 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 #ifdef SAILFISHOS #define KCMUTILS_VERSION 0 #else #include #include #endif #include #include +#include "plugin_runcommand_debug.h" + #define PACKET_TYPE_RUNCOMMAND QStringLiteral("kdeconnect.runcommand") #ifdef Q_OS_WIN #define COMMAND "cmd" #define ARGS "/c" #else #define COMMAND "/bin/sh" #define ARGS "-c" #endif K_PLUGIN_CLASS_WITH_JSON(RunCommandPlugin, "kdeconnect_runcommand.json") -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::receivePacket(const NetworkPacket& 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:" << COMMAND << ARGS << commandJson[QStringLiteral("command")].toString(); QProcess::startDetached(QStringLiteral(COMMAND), QStringList()<< QStringLiteral(ARGS) << commandJson[QStringLiteral("command")].toString()); return true; } else if (np.has(QStringLiteral("setup"))) { QProcess::startDetached(QStringLiteral("kdeconnect-settings"), { QStringLiteral("--args"), QString(device()->id() + QStringLiteral(":kdeconnect_runcommand")) }); } return false; } void RunCommandPlugin::connected() { sendConfig(); } void RunCommandPlugin::sendConfig() { QString commands = config()->get(QStringLiteral("commands"),QStringLiteral("{}")); NetworkPacket np(PACKET_TYPE_RUNCOMMAND, {{QStringLiteral("commandList"), commands}}); #if KCMUTILS_VERSION >= QT_VERSION_CHECK(5, 45, 0) np.set(QStringLiteral("canAddCommand"), true); #endif sendPacket(np); } void RunCommandPlugin::configChanged() { sendConfig(); } #include "runcommandplugin.moc" diff --git a/plugins/screensaver-inhibit/CMakeLists.txt b/plugins/screensaver-inhibit/CMakeLists.txt index 3398a352..ec6353b4 100644 --- a/plugins/screensaver-inhibit/CMakeLists.txt +++ b/plugins/screensaver-inhibit/CMakeLists.txt @@ -1,24 +1,31 @@ if(APPLE) set(kdeconnect_screensaver_inhibit_SRCS screensaverinhibitplugin-macos.cpp ) elseif(WIN32) set(kdeconnect_screensaver_inhibit_SRCS screensaverinhibitplugin-win.cpp ) else() set(kdeconnect_screensaver_inhibit_SRCS screensaverinhibitplugin.cpp ) endif() -kdeconnect_add_plugin(kdeconnect_screensaver_inhibit JSON kdeconnect_screensaver_inhibit.json SOURCES ${kdeconnect_screensaver_inhibit_SRCS}) +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER kdeconnect_screensaverinhibit_debug.h + IDENTIFIER KDECONNECT_PLUGIN_SCREENSAVERINHIBIT CATEGORY_NAME kdeconnect.plugin.screensaverinhibit + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (plugin screensaverinhibit)") + +kdeconnect_add_plugin(kdeconnect_screensaver_inhibit JSON kdeconnect_screensaver_inhibit.json SOURCES ${kdeconnect_screensaver_inhibit_SRCS} ${debug_file_SRCS}) target_link_libraries(kdeconnect_screensaver_inhibit kdeconnectcore) if(NOT APPLE AND NOT WIN32) target_link_libraries(kdeconnect_screensaver_inhibit Qt5::DBus KF5::I18n ) endif() diff --git a/plugins/screensaver-inhibit/screensaverinhibitplugin-macos.cpp b/plugins/screensaver-inhibit/screensaverinhibitplugin-macos.cpp index c89bbc0f..514c9eb1 100644 --- a/plugins/screensaver-inhibit/screensaverinhibitplugin-macos.cpp +++ b/plugins/screensaver-inhibit/screensaverinhibitplugin-macos.cpp @@ -1,61 +1,59 @@ /** * Copyright 2019 Weixuan XIAO * * 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-macos.h" #include -#include +#include "kdeconnect_screensaverinhibit_debug.h" K_PLUGIN_CLASS_WITH_JSON(ScreensaverInhibitPlugin, "kdeconnect_screensaver_inhibit.json") -Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SCREENSAVERINHIBIT, "kdeconnect.plugin.screensaverinhibit") - ScreensaverInhibitPlugin::ScreensaverInhibitPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args), m_caffeinateProcess(nullptr) { if (QFile::exists(QStringLiteral("/usr/bin/caffeinate"))) { m_caffeinateProcess = new QProcess(); m_caffeinateProcess->setProgram(QStringLiteral("caffeinate")); m_caffeinateProcess->setArguments({QStringLiteral("-d")}); // Prevent the display from sleeping m_caffeinateProcess->start(); } else { qWarning(KDECONNECT_PLUGIN_SCREENSAVERINHIBIT) << "Cannot find caffeinate on macOS install"; } } ScreensaverInhibitPlugin::~ScreensaverInhibitPlugin() { if (m_caffeinateProcess != nullptr) { m_caffeinateProcess->terminate(); m_caffeinateProcess = nullptr; } } void ScreensaverInhibitPlugin::connected() { } bool ScreensaverInhibitPlugin::receivePacket(const NetworkPacket& np) { Q_UNUSED(np); return false; } #include "screensaverinhibitplugin-macos.moc" diff --git a/plugins/screensaver-inhibit/screensaverinhibitplugin-win.cpp b/plugins/screensaver-inhibit/screensaverinhibitplugin-win.cpp index 0a8c7613..5480e781 100644 --- a/plugins/screensaver-inhibit/screensaverinhibitplugin-win.cpp +++ b/plugins/screensaver-inhibit/screensaverinhibitplugin-win.cpp @@ -1,49 +1,47 @@ /** * Copyright 2019 Piyush Aggarwal * * 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-win.h" #include -#include #include +#include "kdeconnect_screensaverinhibit_debug.h" K_PLUGIN_CLASS_WITH_JSON(ScreensaverInhibitPlugin, "kdeconnect_screensaver_inhibit.json") -Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SCREENSAVERINHIBIT, "kdeconnect.plugin.screensaverinhibit") - ScreensaverInhibitPlugin::ScreensaverInhibitPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) { SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED); } ScreensaverInhibitPlugin::~ScreensaverInhibitPlugin() { SetThreadExecutionState(ES_CONTINUOUS); } bool ScreensaverInhibitPlugin::receivePacket(const NetworkPacket& np) { Q_UNUSED(np); return false; } #include "screensaverinhibitplugin-win.moc" diff --git a/plugins/screensaver-inhibit/screensaverinhibitplugin.cpp b/plugins/screensaver-inhibit/screensaverinhibitplugin.cpp index ed154797..1b276256 100644 --- a/plugins/screensaver-inhibit/screensaverinhibitplugin.cpp +++ b/plugins/screensaver-inhibit/screensaverinhibitplugin.cpp @@ -1,82 +1,80 @@ /** * 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 "kdeconnect_screensaverinhibit_debug.h" K_PLUGIN_CLASS_WITH_JSON(ScreensaverInhibitPlugin, "kdeconnect_screensaver_inhibit.json") -Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SCREENSAVERINHIBIT, "kdeconnect.plugin.screensaverinhibit") - #define INHIBIT_SERVICE QStringLiteral("org.freedesktop.ScreenSaver") #define INHIBIT_INTERFACE INHIBIT_SERVICE #define INHIBIT_PATH QStringLiteral("/ScreenSaver") #define INHIBIT_METHOD QStringLiteral("Inhibit") #define UNINHIBIT_METHOD QStringLiteral("UnInhibit") #define 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, QStringLiteral("org.kde.kdeconnect.daemon"), i18n("Phone is connected")); if (!reply.errorMessage().isEmpty()) { 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().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::receivePacket(const NetworkPacket& np) { Q_UNUSED(np); return false; } #include "screensaverinhibitplugin.moc" diff --git a/plugins/sendnotifications/CMakeLists.txt b/plugins/sendnotifications/CMakeLists.txt index 94576b42..090c4b81 100644 --- a/plugins/sendnotifications/CMakeLists.txt +++ b/plugins/sendnotifications/CMakeLists.txt @@ -1,47 +1,55 @@ if (SAILFISHOS) set(SN_PLUGIN_ENABLED_BY_DEFAULT "true") else() set(SN_PLUGIN_ENABLED_BY_DEFAULT "false") endif() configure_file(kdeconnect_sendnotifications.json.in kdeconnect_sendnotifications.json) +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER plugin_sendnotification_debug.h + IDENTIFIER KDECONNECT_PLUGIN_SENDNOTIFICATION CATEGORY_NAME kdeconnect.plugin.sendnotification + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (plugin sendnotification)") + set(kdeconnect_sendnotifications_SRCS sendnotificationsplugin.cpp notificationslistener.cpp notifyingapplication.cpp kdeconnect_sendnotifications.json + ${debug_file_SRCS} ) kdeconnect_add_plugin(kdeconnect_sendnotifications JSON "${CMAKE_CURRENT_BINARY_DIR}/kdeconnect_sendnotifications.json" SOURCES ${kdeconnect_sendnotifications_SRCS}) target_link_libraries(kdeconnect_sendnotifications kdeconnectcore Qt5::DBus KF5::I18n Qt5::Gui KF5::IconThemes KF5::ConfigCore ) ####################################### # Config if (NOT SAILFISHOS) set( kdeconnect_sendnotifications_config_SRCS sendnotifications_config.cpp notifyingapplication.cpp notifyingapplicationmodel.cpp ) ki18n_wrap_ui( kdeconnect_sendnotifications_config_SRCS sendnotifications_config.ui ) add_library(kdeconnect_sendnotifications_config MODULE ${kdeconnect_sendnotifications_config_SRCS} ) target_link_libraries( kdeconnect_sendnotifications_config kdeconnectcore kdeconnectpluginkcm KF5::I18n KF5::KCMUtils ) install( TARGETS kdeconnect_sendnotifications_config DESTINATION ${PLUGIN_INSTALL_DIR} ) install( FILES kdeconnect_sendnotifications_config.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) endif() diff --git a/plugins/sendnotifications/notificationslistener.cpp b/plugins/sendnotifications/notificationslistener.cpp index bfbea38e..fdb10f75 100644 --- a/plugins/sendnotifications/notificationslistener.cpp +++ b/plugins/sendnotifications/notificationslistener.cpp @@ -1,280 +1,279 @@ /** * 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 "notificationslistener.h" #include #include #include -#include #include #include #include #include #include #include #include #include #include #include "sendnotificationsplugin.h" -#include "sendnotification_debug.h" +#include "plugin_sendnotification_debug.h" #include "notifyingapplication.h" //In older Qt released, qAsConst isnt available #include "qtcompat_p.h" NotificationsListener::NotificationsListener(KdeConnectPlugin* aPlugin) : QDBusAbstractAdaptor(aPlugin), m_plugin(aPlugin) { qRegisterMetaTypeStreamOperators("NotifyingApplication"); bool ret = DBusHelper::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() << ":" << DBusHelper::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"), QStringLiteral("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"), QStringLiteral("interface='org.freedesktop.Notifications',member='Notify',type='method_call',eavesdrop='true'")); DBusHelper::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 + QStringLiteral(".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)}, {QStringLiteral("appName"), appName}, {QStringLiteral("ticker"), ticker}, {QStringLiteral("isClearable"), timeout == 0} }); // KNotifications are persistent if // timeout == 0, for other notifications // clearability is pointless // sync any icon data? if (m_plugin->config()->get(QStringLiteral("generalSynchronizeIcons"), true)) { QSharedPointer iconSource; // try different image sources according to priorities in notifications- // spec version 1.2: if (hints.contains(QStringLiteral("image-data"))) iconSource = iconForImageData(hints[QStringLiteral("image-data")]); else if (hints.contains(QStringLiteral("image_data"))) // 1.1 backward compatibility iconSource = iconForImageData(hints[QStringLiteral("image_data")]); else if (hints.contains(QStringLiteral("image-path"))) iconSource = iconForIconName(hints[QStringLiteral("image-path")].toString()); else if (hints.contains(QStringLiteral("image_path"))) // 1.1 backward compatibility iconSource = iconForIconName(hints[QStringLiteral("image_path")].toString()); else if (!appIcon.isEmpty()) iconSource = iconForIconName(appIcon); else if (hints.contains(QStringLiteral("icon_data"))) // < 1.1 backward compatibility iconSource = iconForImageData(hints[QStringLiteral("icon_data")]); if (iconSource) np.setPayload(iconSource, iconSource->size()); } m_plugin->sendPacket(np); return (replacesId > 0 ? replacesId : id); } diff --git a/plugins/sendnotifications/sendnotification_debug.h b/plugins/sendnotifications/sendnotification_debug.h deleted file mode 100644 index e0cee05d..00000000 --- a/plugins/sendnotifications/sendnotification_debug.h +++ /dev/null @@ -1,28 +0,0 @@ -/** - * 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 SENDNOTIFICATION_DEBUG_H -#define SENDNOTIFICATION_DEBUG_H - -#include - -Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SENDNOTIFICATION) - -#endif //NOTIFICATION_DEBUG_H diff --git a/plugins/sendnotifications/sendnotificationsplugin.cpp b/plugins/sendnotifications/sendnotificationsplugin.cpp index 602f8a05..1b130109 100644 --- a/plugins/sendnotifications/sendnotificationsplugin.cpp +++ b/plugins/sendnotifications/sendnotificationsplugin.cpp @@ -1,54 +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 "sendnotificationsplugin.h" #include "notificationslistener.h" -#include "sendnotification_debug.h" +#include "plugin_sendnotification_debug.h" #include K_PLUGIN_CLASS_WITH_JSON(SendNotificationsPlugin, "kdeconnect_sendnotifications.json") -Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SENDNOTIFICATION, "kdeconnect.plugin.sendnotification") - SendNotificationsPlugin::SendNotificationsPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) { notificationsListener = new NotificationsListener(this); } SendNotificationsPlugin::~SendNotificationsPlugin() { delete notificationsListener; } bool SendNotificationsPlugin::receivePacket(const NetworkPacket& np) { Q_UNUSED(np); return true; } void SendNotificationsPlugin::connected() { } #include "sendnotificationsplugin.moc" diff --git a/plugins/sftp/CMakeLists.txt b/plugins/sftp/CMakeLists.txt index 2066102e..beb17743 100644 --- a/plugins/sftp/CMakeLists.txt +++ b/plugins/sftp/CMakeLists.txt @@ -1,26 +1,33 @@ # Find fusermount -- otherwise fallback to umount find_program(HAVE_FUSERMOUNT fusermount) configure_file(config-sftp.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-sftp.h ) if(WIN32) set(kdeconnect_sftp_SRCS sftpplugin-win.cpp ) else() set(kdeconnect_sftp_SRCS mounter.cpp mountloop.cpp sftpplugin.cpp ) endif() -kdeconnect_add_plugin(kdeconnect_sftp JSON kdeconnect_sftp.json SOURCES ${kdeconnect_sftp_SRCS}) +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER plugin_sftp_debug.h + IDENTIFIER KDECONNECT_PLUGIN_SFTP CATEGORY_NAME kdeconnect.plugin.sftp + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (plugin sftp)") + +kdeconnect_add_plugin(kdeconnect_sftp JSON kdeconnect_sftp.json SOURCES ${kdeconnect_sftp_SRCS} ${debug_file_SRCS}) target_link_libraries(kdeconnect_sftp kdeconnectcore Qt5::DBus KF5::I18n KF5::KIOFileWidgets KF5::KIOWidgets KF5::Notifications ) diff --git a/plugins/sftp/mounter.cpp b/plugins/sftp/mounter.cpp index fca9907c..b435a56b 100644 --- a/plugins/sftp/mounter.cpp +++ b/plugins/sftp/mounter.cpp @@ -1,261 +1,261 @@ /** * 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 "plugin_sftp_debug.h" #include "kdeconnectconfig.h" Mounter::Mounter(SftpPlugin* sftp) : QObject(sftp) , m_sftp(sftp) , m_proc(nullptr) , m_mountPoint(sftp->mountPoint()) , m_started(false) { connect(m_sftp, &SftpPlugin::packetReceived, this, &Mounter::onPackageReceived); connect(&m_connectTimer, &QTimer::timeout, this, &Mounter::onMountTimeout); 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, &Mounter::start); qCDebug(KDECONNECT_PLUGIN_SFTP) << "Created mounter"; } Mounter::~Mounter() { 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, &Mounter::mounted, &loop, &MountLoop::successed); connect(this, &Mounter::failed, &loop, &MountLoop::failed); return loop.exec(); } void Mounter::onPackageReceived(const NetworkPacket& np) { if (np.get(QStringLiteral("stop"), false)) { qCDebug(KDECONNECT_PLUGIN_SFTP) << "SFTP server stopped"; unmount(false); return; } if (np.has(QStringLiteral("errorMessage"))) { Q_EMIT failed(np.get(QStringLiteral("errorMessage"))); 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(); */ unmount(false); m_proc = new KProcess(); m_proc->setOutputChannelMode(KProcess::MergedChannels); connect(m_proc, &QProcess::started, this, &Mounter::onStarted); connect(m_proc, &QProcess::errorOccurred, this, &Mounter::onError); connect(m_proc, QOverload::of(&QProcess::finished), this, &Mounter::onFinished); QDir().mkpath(m_mountPoint); const QString program = QStringLiteral("sshfs"); QString path; if (np.has(QStringLiteral("multiPaths"))) path = QStringLiteral("/"); 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() << QStringLiteral("%1@%2:%3").arg( np.get(QStringLiteral("user")), ip, path) << m_mountPoint << 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") << QStringLiteral("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("reconnect") << QStringLiteral("-o") << QStringLiteral("ServerAliveInterval=30") << QStringLiteral("-o") << QStringLiteral("password_stdin") ; m_proc->setProgram(program, arguments); 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(); //m_proc->setStandardOutputFile("/tmp/kdeconnect-sftp.out"); //m_proc->setStandardErrorFile("/tmp/kdeconnect-sftp.err"); auto proc = m_proc; connect(m_proc, &KProcess::readyReadStandardError, this, [proc]() { qCDebug(KDECONNECT_PLUGIN_SFTP) << "stderr: " << proc->readAll(); }); connect(m_proc, &KProcess::readyReadStandardOutput, this, [proc]() { qCDebug(KDECONNECT_PLUGIN_SFTP) << "stdout:" << proc->readAll(); }); } void Mounter::onError(QProcess::ProcessError error) { if (error == QProcess::FailedToStart) { qCDebug(KDECONNECT_PLUGIN_SFTP) << "sshfs process failed to start"; m_started = false; Q_EMIT failed(i18n("Failed to start sshfs")); } else if(error == QProcess::ProcessError::Crashed) { qCDebug(KDECONNECT_PLUGIN_SFTP) << "sshfs process crashed"; m_started = false; Q_EMIT failed(i18n("sshfs process crashed")); } else { qCDebug(KDECONNECT_PLUGIN_SFTP) << "sshfs process error" << error; m_started = false; Q_EMIT failed(i18n("Unknown error in sshfs")); } } void Mounter::onFinished(int exitCode, QProcess::ExitStatus exitStatus) { if (exitStatus == QProcess::NormalExit && exitCode == 0) { 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 filesystem. sshfs finished with exit code %0").arg(exitCode)); } 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() { NetworkPacket np(PACKET_TYPE_SFTP_REQUEST, {{QStringLiteral("startBrowsing"), true}}); m_sftp->sendPacket(np); m_connectTimer.start(); } void Mounter::unmount(bool finished) { qCDebug(KDECONNECT_PLUGIN_SFTP) << "Unmount" << m_proc; if (m_proc) { if (!finished) { //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; } diff --git a/plugins/sftp/sftp_debug.h b/plugins/sftp/sftp_debug.h deleted file mode 100644 index 89a2de05..00000000 --- a/plugins/sftp/sftp_debug.h +++ /dev/null @@ -1,28 +0,0 @@ -/** - * 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 diff --git a/plugins/sftp/sftpplugin-win.cpp b/plugins/sftp/sftpplugin-win.cpp index 10959b53..859fc9c4 100644 --- a/plugins/sftp/sftpplugin-win.cpp +++ b/plugins/sftp/sftpplugin-win.cpp @@ -1,94 +1,92 @@ /** * Copyright 2019 Piyush Aggarwal * * 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-win.h" #include #include #include #include #include #include #include #include -#include "sftp_debug.h" +#include "plugin_sftp_debug.h" K_PLUGIN_CLASS_WITH_JSON(SftpPlugin, "kdeconnect_sftp.json") -Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SFTP, "kdeconnect.plugin.sftp") - SftpPlugin::SftpPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) { deviceId = device()->id(); } SftpPlugin::~SftpPlugin(){} bool SftpPlugin::startBrowsing() { NetworkPacket np(PACKET_TYPE_SFTP_REQUEST, {{QStringLiteral("startBrowsing"), true}}); sendPacket(np); return false; } bool SftpPlugin::receivePacket(const NetworkPacket& np) { if (!(expectedFields - np.body().keys().toSet()).isEmpty()) { qCWarning(KDECONNECT_PLUGIN_SFTP) << "Invalid packet received."; for (QString missingField: (expectedFields - np.body().keys().toSet()).toList()) { qCWarning(KDECONNECT_PLUGIN_SFTP) << "Field" << missingField << "missing from packet."; } return false; } if (np.has(QStringLiteral("errorMessage"))) { qCWarning(KDECONNECT_PLUGIN_SFTP) << np.get(QStringLiteral("errorMessage")); return false; } QString path; if (np.has(QStringLiteral("multiPaths"))) { path = QStringLiteral("/"); } else { path = np.get(QStringLiteral("path")); } QString url_string = QStringLiteral("sftp://%1:%2@%3:%4%5").arg( np.get(QStringLiteral("user")), np.get(QStringLiteral("password")), np.get(QStringLiteral("ip")), np.get(QStringLiteral("port")), path ); static QRegularExpression uriRegex(QStringLiteral("^sftp://kdeconnect:\\w+@\\d+.\\d+.\\d+.\\d+:17[3-6][0-9]/$")); if (!uriRegex.match(url_string).hasMatch()) { qCWarning(KDECONNECT_PLUGIN_SFTP) << "Invalid URL invoked. If the problem persists, contact the developers."; } if (!QDesktopServices::openUrl(QUrl(url_string))) { QMessageBox::critical(nullptr, i18n("KDE Connect"), i18n("Cannot handle SFTP protocol. Apologies for the inconvenience"), QMessageBox::Abort, QMessageBox::Abort); } return true; } #include "sftpplugin-win.moc" diff --git a/plugins/sftp/sftpplugin.cpp b/plugins/sftp/sftpplugin.cpp index 9fb35ff8..37d15c70 100644 --- a/plugins/sftp/sftpplugin.cpp +++ b/plugins/sftp/sftpplugin.cpp @@ -1,201 +1,199 @@ /** * 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" +#include "plugin_sftp_debug.h" K_PLUGIN_CLASS_WITH_JSON(SftpPlugin, "kdeconnect_sftp.json") -Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SFTP, "kdeconnect.plugin.sftp") - static const QSet fields_c = QSet() << QStringLiteral("ip") << QStringLiteral("port") << QStringLiteral("user") << QStringLiteral("port") << QStringLiteral("path"); struct SftpPlugin::Pimpl { Pimpl() : m_mounter(nullptr) {} //Add KIO entry to Dolphin's Places KFilePlacesModel m_placesModel; Mounter* m_mounter; }; SftpPlugin::SftpPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , d(new Pimpl()) { deviceId = device()->id(); addToDolphin(); qCDebug(KDECONNECT_PLUGIN_SFTP) << "Created device:" << device()->name(); } SftpPlugin::~SftpPlugin() { removeFromDolphin(); unmount(); } void SftpPlugin::addToDolphin() { removeFromDolphin(); QUrl kioUrl(QStringLiteral("kdeconnect://") + deviceId + QStringLiteral("/")); d->m_placesModel.addPlace(device()->name(), kioUrl, QStringLiteral("kdeconnect")); qCDebug(KDECONNECT_PLUGIN_SFTP) << "add to dolphin"; } void SftpPlugin::removeFromDolphin() { QUrl kioUrl(QStringLiteral("kdeconnect://") + deviceId + QStringLiteral("/")); QModelIndex index = d->m_placesModel.closestItem(kioUrl); while (index.row() != -1) { d->m_placesModel.removePlace(index); index = d->m_placesModel.closestItem(kioUrl); } } void SftpPlugin::mount() { qCDebug(KDECONNECT_PLUGIN_SFTP) << "Mount device:" << device()->name(); if (d->m_mounter) { return; } 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 (d->m_mounter) { d->m_mounter->deleteLater(); d->m_mounter = nullptr; } } bool SftpPlugin::mountAndWait() { mount(); return d->m_mounter->wait(); } bool SftpPlugin::isMounted() const { return d->m_mounter && d->m_mounter->isMounted(); } QString SftpPlugin::getMountError() { if (!mountError.isEmpty()) { return mountError; } return QString(); } bool SftpPlugin::startBrowsing() { if (mountAndWait()) { //return new KRun(QUrl::fromLocalFile(mountPoint()), 0); return new KRun(QUrl(QStringLiteral("kdeconnect://") + deviceId), nullptr); } return false; } bool SftpPlugin::receivePacket(const NetworkPacket& np) { if (!(fields_c - np.body().keys().toSet()).isEmpty() && !np.has(QStringLiteral("errorMessage"))) { // packet is invalid return false; } Q_EMIT packetReceived(np); remoteDirectories.clear(); 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() + QStringLiteral("/DCIM/Camera"), i18n("Camera pictures")); } return true; } QString SftpPlugin::mountPoint() { 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() << 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) { mountError = message; KNotification::event(KNotification::Error, device()->name(), message); unmount(); Q_EMIT unmounted(); } QVariantMap SftpPlugin::getDirectories() { return remoteDirectories; } #include "sftpplugin.moc" diff --git a/plugins/share/CMakeLists.txt b/plugins/share/CMakeLists.txt index bb47e987..a4a40ad4 100644 --- a/plugins/share/CMakeLists.txt +++ b/plugins/share/CMakeLists.txt @@ -1,33 +1,41 @@ +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER plugin_share_debug.h + IDENTIFIER KDECONNECT_PLUGIN_SHARE CATEGORY_NAME kdeconnect.plugin.share + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (plugin share)") + set(kdeconnect_share_SRCS shareplugin.cpp + ${debug_file_SRCS} ) 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 KF5::Service ) ####################################### # 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/share_debug.h b/plugins/share/share_debug.h deleted file mode 100644 index 6097e737..00000000 --- a/plugins/share/share_debug.h +++ /dev/null @@ -1,28 +0,0 @@ -/** - * 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 diff --git a/plugins/share/shareplugin.cpp b/plugins/share/shareplugin.cpp index 92c5364f..ec9e4c44 100644 --- a/plugins/share/shareplugin.cpp +++ b/plugins/share/shareplugin.cpp @@ -1,240 +1,238 @@ /** * 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 #include #include "core/filetransferjob.h" #include "core/daemon.h" +#include "plugin_share_debug.h" K_PLUGIN_CLASS_WITH_JSON(SharePlugin, "kdeconnect_share.json") -Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SHARE, "kdeconnect.plugin.share") - SharePlugin::SharePlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , m_compositeJob() { } QUrl SharePlugin::destinationDir() const { const QString defaultDownloadPath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); QUrl dir = QUrl::fromLocalFile(config()->get(QStringLiteral("incoming_path"), defaultDownloadPath)); 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; } QUrl SharePlugin::getFileDestination(const QString filename) const { const QUrl dir = destinationDir().adjusted(QUrl::StripTrailingSlash); QUrl destination(dir); destination.setPath(dir.path() + QStringLiteral("/") + filename, QUrl::DecodedMode); if (destination.isLocalFile() && QFile::exists(destination.toLocalFile())) { destination.setPath(dir.path() + QStringLiteral("/") + KFileUtils::suggestName(dir, filename), QUrl::DecodedMode); } return destination; } static QString cleanFilename(const QString &filename) { int idx = filename.lastIndexOf(QLatin1Char('/')); return idx>=0 ? filename.mid(idx + 1) : filename; } void SharePlugin::setDateModified(const QUrl& destination, const qint64 timestamp) { QFile receivedFile(destination.toLocalFile()); if (!receivedFile.exists() || !receivedFile.open(QIODevice::ReadWrite | QIODevice::Text)) { return; } receivedFile.setFileTime(QDateTime::fromMSecsSinceEpoch(timestamp), QFileDevice::FileTime(QFileDevice::FileModificationTime)); } bool SharePlugin::receivePacket(const NetworkPacket& np) { /* //TODO: Write a test like this if (np.type() == PACKET_TYPE_PING) { qCDebug(KDECONNECT_PLUGIN_SHARE) << "sending file" << (QDesktopServices::storageLocation(QDesktopServices::HomeLocation) + "/.bashrc"); NetworkPacket out(PACKET_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()->sendPacket(out); return true; } */ qCDebug(KDECONNECT_PLUGIN_SHARE) << "File transfer"; if (np.hasPayload() || np.has(QStringLiteral("filename"))) { // qCDebug(KDECONNECT_PLUGIN_SHARE) << "receiving file" << filename << "in" << dir << "into" << destination; const QString filename = cleanFilename(np.get(QStringLiteral("filename"), QString::number(QDateTime::currentMSecsSinceEpoch()))); QUrl destination = getFileDestination(filename); if (np.hasPayload()) { qint64 dateModified = np.get(QStringLiteral("lastModified"), QDateTime::currentMSecsSinceEpoch()); const bool open = np.get(QStringLiteral("open"), false); if (!m_compositeJob) { m_compositeJob = new CompositeFileTransferJob(device()->id()); KIO::getJobTracker()->registerJob(m_compositeJob); } FileTransferJob* job = np.createPayloadTransferJob(destination); job->setOriginName(device()->name() + QStringLiteral(": ") + filename); connect(job, &KJob::result, this, [this, dateModified, open] (KJob* job) -> void { finished(job, dateModified, open); }); m_compositeJob->addSubjob(job); if (!m_compositeJob->isRunning()) { m_compositeJob->start(); } } else { QFile file(destination.toLocalFile()); file.open(QIODevice::WriteOnly); file.close(); } } else if (np.has(QStringLiteral("text"))) { QString text = np.get(QStringLiteral("text")); KService::Ptr service = KMimeTypeTrader::self()->preferredService(QStringLiteral("text/plain")); const QString defaultApp = service ? service->desktopEntryName() : QString(); if (defaultApp == QLatin1String("org.kde.kate") || defaultApp == QLatin1String("org.kde.kwrite")) { QProcess* proc = new QProcess(); connect(proc, SIGNAL(finished(int)), proc, SLOT(deleteLater())); proc->start(defaultApp.section(QStringLiteral("."), 2,2), QStringList(QStringLiteral("--stdin"))); proc->write(text.toUtf8()); proc->closeWriteChannel(); } else { QTemporaryFile tmpFile; tmpFile.setFileTemplate(QStringLiteral("kdeconnect-XXXXXX.txt")); tmpFile.setAutoRemove(false); tmpFile.open(); tmpFile.write(text.toUtf8()); tmpFile.close(); const QString fileName = tmpFile.fileName(); Q_EMIT shareReceived(fileName); QDesktopServices::openUrl(QUrl::fromLocalFile(fileName)); } } 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, const qint64 dateModified, const bool open) { FileTransferJob* ftjob = qobject_cast(job); if (ftjob && !job->error()) { Q_EMIT shareReceived(ftjob->destination().toString()); setDateModified(ftjob->destination(), dateModified); qCDebug(KDECONNECT_PLUGIN_SHARE) << "File transfer finished." << ftjob->destination(); if (open) { QDesktopServices::openUrl(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, bool open) { NetworkPacket packet(PACKET_TYPE_SHARE_REQUEST); if (url.isLocalFile()) { QSharedPointer ioFile(new QFile(url.toLocalFile())); if (!ioFile->exists()) { Daemon::instance()->reportError(i18n("Could not share file"), i18n("%1 does not exist", url.toLocalFile())); return; } else { packet.setPayload(ioFile, ioFile->size()); packet.set(QStringLiteral("filename"), QUrl(url).fileName()); packet.set(QStringLiteral("open"), open); } } else { packet.set(QStringLiteral("url"), url.toString()); } sendPacket(packet); } void SharePlugin::shareUrls(const QStringList& urls) { for(const QString& url : urls) { shareUrl(QUrl(url), false); } } void SharePlugin::shareText(const QString& text) { NetworkPacket packet(PACKET_TYPE_SHARE_REQUEST); packet.set(QStringLiteral("text"), text); sendPacket(packet); } QString SharePlugin::dbusPath() const { return QStringLiteral("/modules/kdeconnect/devices/") + device()->id() + QStringLiteral("/share"); } #include "shareplugin.moc" diff --git a/plugins/sms/CMakeLists.txt b/plugins/sms/CMakeLists.txt index 96910589..68cf67bf 100644 --- a/plugins/sms/CMakeLists.txt +++ b/plugins/sms/CMakeLists.txt @@ -1,23 +1,37 @@ +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER plugin_sms_debug.h + IDENTIFIER KDECONNECT_PLUGIN_SMS CATEGORY_NAME kdeconnect.plugin.sms + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (plugin sms)") + +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER kdeconnect_conversations_debug.h + IDENTIFIER KDECONNECT_CONVERSATIONS CATEGORY_NAME kdeconnect.conversations + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (conversations)") + set(kdeconnect_sms_SRCS smsplugin.cpp conversationsdbusinterface.cpp requestconversationworker.cpp + ${debug_file_SRCS} ) include_directories(${CMAKE_BINARY_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../notifications/) # needed for the sendreplydialog ki18n_wrap_ui(kdeconnect_sms_SRCS ../notifications/sendreplydialog.ui) kdeconnect_add_plugin(kdeconnect_sms JSON kdeconnect_sms.json SOURCES ../notifications/sendreplydialog.cpp ${kdeconnect_sms_SRCS}) target_link_libraries(kdeconnect_sms kdeconnectcore kdeconnectinterfaces Qt5::DBus KF5::I18n KF5::Notifications Qt5::Widgets ) diff --git a/plugins/sms/conversationsdbusinterface.cpp b/plugins/sms/conversationsdbusinterface.cpp index 1be3b02d..1806230d 100644 --- a/plugins/sms/conversationsdbusinterface.cpp +++ b/plugins/sms/conversationsdbusinterface.cpp @@ -1,225 +1,225 @@ /** * Copyright 2018 Simon Redman * * 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 "conversationsdbusinterface.h" #include "interfaces/dbusinterfaces.h" #include "interfaces/conversationmessage.h" #include "requestconversationworker.h" #include #include #include -Q_LOGGING_CATEGORY(KDECONNECT_CONVERSATIONS, "kdeconnect.conversations") +#include "kdeconnect_conversations_debug.h" QMap ConversationsDbusInterface::liveConversationInterfaces; ConversationsDbusInterface::ConversationsDbusInterface(KdeConnectPlugin* plugin) : QDBusAbstractAdaptor(const_cast(plugin->device())) , m_device(plugin->device()->id()) , m_plugin(plugin) , m_lastId(0) , m_smsInterface(m_device) { ConversationMessage::registerDbusType(); // Check for an existing interface for the same device // If there is already an interface for this device, we can safely delete is since we have just replaced it const auto& oldInterfaceItr = ConversationsDbusInterface::liveConversationInterfaces.find(m_device); if (oldInterfaceItr != ConversationsDbusInterface::liveConversationInterfaces.end()) { ConversationsDbusInterface* oldInterface = oldInterfaceItr.value(); oldInterface->deleteLater(); ConversationsDbusInterface::liveConversationInterfaces.erase(oldInterfaceItr); } ConversationsDbusInterface::liveConversationInterfaces[m_device] = this; } ConversationsDbusInterface::~ConversationsDbusInterface() { // Wake all threads which were waiting for a reply from this interface // This might result in some noise on dbus, but it's better than leaking a bunch of resources! waitingForMessagesLock.lock(); conversationsWaitingForMessages.clear(); waitingForMessages.wakeAll(); waitingForMessagesLock.unlock(); // Erase this interface from the list of known interfaces const auto myIterator = ConversationsDbusInterface::liveConversationInterfaces.find(m_device); ConversationsDbusInterface::liveConversationInterfaces.erase(myIterator); } QVariantList ConversationsDbusInterface::activeConversations() { QList toReturn; toReturn.reserve(m_conversations.size()); for (auto it = m_conversations.cbegin(); it != m_conversations.cend(); ++it) { const auto& conversation = it.value().values(); if (conversation.isEmpty()) { // This should really never happen because we create a conversation at the same time // as adding a message, but better safe than sorry qCWarning(KDECONNECT_CONVERSATIONS) << "Conversation with ID" << it.key() << "is unexpectedly empty"; break; } const QVariant& message = QVariant::fromValue(*conversation.crbegin()); toReturn.append(message); } return toReturn; } void ConversationsDbusInterface::requestConversation(const qint64& conversationID, int start, int end) { if (start < 0 || end < 0) { qCWarning(KDECONNECT_CONVERSATIONS) << "requestConversation" << "Start and end must be >= 0"; return; } if (end - start < 0) { qCWarning(KDECONNECT_CONVERSATIONS) << "requestConversation" <<"Start must be before end"; return; } RequestConversationWorker* worker = new RequestConversationWorker(conversationID, start, end, this); connect(worker, &RequestConversationWorker::conversationMessageRead, this, &ConversationsDbusInterface::conversationUpdated, Qt::QueuedConnection); worker->work(); } void ConversationsDbusInterface::addMessages(const QList &messages) { QSet updatedConversationIDs; for (const auto& message : messages) { const qint32& threadId = message.threadID(); // We might discover that there are no new messages in this conversation, thus calling it // "updated" might turn out to be a bit misleading // However, we need to report it as updated regardless, for the case where we have already // cached every message of the conversation but we have received a request for more, otherwise // we will never respond to that request updatedConversationIDs.insert(message.threadID()); if (m_known_messages[threadId].contains(message.uID())) { // This message has already been processed. Don't do anything. continue; } // Store the Message in the list corresponding to its thread bool newConversation = !m_conversations.contains(threadId); const auto& threadPosition = m_conversations[threadId].insert(message.date(), message); m_known_messages[threadId].insert(message.uID()); // If this message was inserted at the end of the list, it is the latest message in the conversation bool latestMessage = threadPosition == m_conversations[threadId].end() - 1; // Tell the world about what just happened if (newConversation) { Q_EMIT conversationCreated(QDBusVariant(QVariant::fromValue(message))); } else if (latestMessage) { Q_EMIT conversationUpdated(QDBusVariant(QVariant::fromValue(message))); } } // It feels bad to go through the set of updated conversations again, // but also there are not many times that updatedConversationIDs will be more than one for (qint64 conversationID : updatedConversationIDs) { quint64 numMessages = m_known_messages[conversationID].size(); Q_EMIT conversationLoaded(conversationID, numMessages); } waitingForMessagesLock.lock(); // Remove the waiting flag for all conversations which we just processed conversationsWaitingForMessages.subtract(updatedConversationIDs); waitingForMessages.wakeAll(); waitingForMessagesLock.unlock(); } void ConversationsDbusInterface::removeMessage(const QString& internalId) { // TODO: Delete the specified message from our internal structures Q_UNUSED(internalId); } QList ConversationsDbusInterface::getConversation(const qint64& conversationID) const { return m_conversations.value(conversationID).values(); } void ConversationsDbusInterface::updateConversation(const qint64& conversationID) { waitingForMessagesLock.lock(); if (conversationsWaitingForMessages.contains(conversationID)) { // This conversation is already being waited on, don't allow more than one thread to wait at a time qCDebug(KDECONNECT_CONVERSATIONS) << "Not allowing two threads to wait for conversationID" << conversationID; waitingForMessagesLock.unlock(); return; } qCDebug(KDECONNECT_CONVERSATIONS) << "Requesting conversation with ID" << conversationID << "from remote"; conversationsWaitingForMessages.insert(conversationID); m_smsInterface.requestConversation(conversationID); while (conversationsWaitingForMessages.contains(conversationID)) { waitingForMessages.wait(&waitingForMessagesLock); } waitingForMessagesLock.unlock(); } void ConversationsDbusInterface::replyToConversation(const qint64& conversationID, const QString& message) { const auto messagesList = m_conversations[conversationID]; if (messagesList.isEmpty()) { qCWarning(KDECONNECT_CONVERSATIONS) << "Got a conversationID for a conversation with no messages!"; return; } if (messagesList.first().isMultitarget()) { qWarning(KDECONNECT_CONVERSATIONS) << "Tried to reply to a group MMS which is not supported in this version of KDE Connect"; return; } const QList& addresses = messagesList.first().addresses(); if (addresses.size() > 1) { // TODO: Upgrade for multitarget replies qCWarning(KDECONNECT_CONVERSATIONS) << "Sending replies to multiple recipients is not supported"; return; } m_smsInterface.sendSms(addresses[0].address(), message, messagesList.first().subID()); } void ConversationsDbusInterface::sendWithoutConversation(const QDBusVariant& addressList, const QString& message) { QList addresses = ConversationAddress::listfromDBus(addressList); m_smsInterface.sendSms(addresses[0].address(), message); } void ConversationsDbusInterface::requestAllConversationThreads() { // Prepare the list of conversations by requesting the first in every thread m_smsInterface.requestAllConversations(); } QString ConversationsDbusInterface::newId() { return QString::number(++m_lastId); } diff --git a/plugins/sms/smsplugin.cpp b/plugins/sms/smsplugin.cpp index 5c926de9..2b4f6667 100644 --- a/plugins/sms/smsplugin.cpp +++ b/plugins/sms/smsplugin.cpp @@ -1,135 +1,133 @@ /** * Copyright 2013 Albert Vaca * Copyright 2018 Simon Redman * * 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 "smsplugin.h" #include #include #include #include -#include #include #include #include #include "sendreplydialog.h" +#include "plugin_sms_debug.h" K_PLUGIN_CLASS_WITH_JSON(SmsPlugin, "kdeconnect_sms.json") -Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SMS, "kdeconnect.plugin.sms") - SmsPlugin::SmsPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , m_telepathyInterface(QStringLiteral("org.freedesktop.Telepathy.ConnectionManager.kdeconnect"), QStringLiteral("/kdeconnect")) , m_conversationInterface(new ConversationsDbusInterface(this)) { } SmsPlugin::~SmsPlugin() { // m_conversationInterface is self-deleting, see ~ConversationsDbusInterface for more information } bool SmsPlugin::receivePacket(const NetworkPacket& np) { if (np.type() == PACKET_TYPE_SMS_MESSAGES) { return handleBatchMessages(np); } return true; } void SmsPlugin::sendSms(const QString& phoneNumber, const QString& messageBody, const qint64 subID) { QVariantMap packetMap({ {QStringLiteral("sendSms"), true}, {QStringLiteral("phoneNumber"), phoneNumber}, {QStringLiteral("messageBody"), messageBody} }); if (subID != -1) { packetMap[QStringLiteral("subID")] = subID; } NetworkPacket np(PACKET_TYPE_SMS_REQUEST, packetMap); qCDebug(KDECONNECT_PLUGIN_SMS) << "Dispatching SMS send request to remote"; sendPacket(np); } void SmsPlugin::requestAllConversations() { NetworkPacket np(PACKET_TYPE_SMS_REQUEST_CONVERSATIONS); sendPacket(np); } void SmsPlugin::requestConversation (const qint64& conversationID) const { NetworkPacket np(PACKET_TYPE_SMS_REQUEST_CONVERSATION); np.set(QStringLiteral("threadID"), conversationID); sendPacket(np); } void SmsPlugin::forwardToTelepathy(const ConversationMessage& message) { // If we don't have a valid Telepathy interface, bail out if (!(m_telepathyInterface.isValid())) return; qCDebug(KDECONNECT_PLUGIN_SMS) << "Passing a text message to the telepathy interface"; connect(&m_telepathyInterface, SIGNAL(messageReceived(QString,QString)), SLOT(sendSms(QString,QString)), Qt::UniqueConnection); const QString messageBody = message.body(); const QString contactName; // TODO: When telepathy support is improved, look up the contact with KPeople const QString phoneNumber = message.addresses()[0].address(); m_telepathyInterface.call(QDBus::NoBlock, QStringLiteral("sendMessage"), phoneNumber, contactName, messageBody); } bool SmsPlugin::handleBatchMessages(const NetworkPacket& np) { const auto messages = np.get(QStringLiteral("messages")); QList messagesList; messagesList.reserve(messages.count()); for (const QVariant& body : messages) { ConversationMessage message(body.toMap()); if (message.containsTextBody()) { forwardToTelepathy(message); } messagesList.append(message); } m_conversationInterface->addMessages(messagesList); return true; } QString SmsPlugin::dbusPath() const { return QStringLiteral("/modules/kdeconnect/devices/") + device()->id() + QStringLiteral("/sms"); } void SmsPlugin::launchApp() { QProcess::startDetached(QLatin1String("kdeconnect-sms"), { QStringLiteral("--device"), device()->id() }); } #include "smsplugin.moc" diff --git a/plugins/systemvolume/CMakeLists.txt b/plugins/systemvolume/CMakeLists.txt index cbbf1b37..49123497 100644 --- a/plugins/systemvolume/CMakeLists.txt +++ b/plugins/systemvolume/CMakeLists.txt @@ -1,35 +1,41 @@ if(WIN32) set(kdeconnect_systemvolume_SRCS systemvolumeplugin-win.cpp ) elseif(APPLE) set(kdeconnect_systemvolume_SRCS systemvolumeplugin-macos.cpp ) else() set(kdeconnect_systemvolume_SRCS systemvolumeplugin-pulse.cpp ) endif() -kdeconnect_add_plugin(kdeconnect_systemvolume JSON kdeconnect_systemvolume.json SOURCES ${kdeconnect_systemvolume_SRCS}) +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER plugin_systemvolume_debug.h + IDENTIFIER KDECONNECT_PLUGIN_SYSTEMVOLUME CATEGORY_NAME kdeconnect.plugin.systemvolume + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (plugin systemvolume)") +kdeconnect_add_plugin(kdeconnect_systemvolume JSON kdeconnect_systemvolume.json SOURCES ${kdeconnect_systemvolume_SRCS} ${debug_file_SRCS}) if(WIN32) target_link_libraries(kdeconnect_systemvolume kdeconnectcore Qt5::Core ole32 ) elseif(APPLE) target_link_libraries(kdeconnect_systemvolume kdeconnectcore Qt5::Core "-framework CoreAudio" ) else() target_link_libraries(kdeconnect_systemvolume kdeconnectcore Qt5::Core KF5::PulseAudioQt ) endif() diff --git a/plugins/systemvolume/systemvolumeplugin-macos.cpp b/plugins/systemvolume/systemvolumeplugin-macos.cpp index 5fa5ee93..b34c66c8 100644 --- a/plugins/systemvolume/systemvolumeplugin-macos.cpp +++ b/plugins/systemvolume/systemvolumeplugin-macos.cpp @@ -1,445 +1,444 @@ /** * Copyright 2019 Weixuan XIAO * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "systemvolumeplugin-macos.h" #include #include -#include #include #include #include +#include "plugin_systemvolume_debug.h" K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_systemvolume.json", registerPlugin< SystemvolumePlugin >(); ) -Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SYSTEMVOLUME, "kdeconnect.plugin.systemvolume") class MacOSCoreAudioDevice { private: AudioDeviceID m_deviceId; QString m_description; bool m_isStereo; friend class SystemvolumePlugin; public: MacOSCoreAudioDevice(AudioDeviceID); ~MacOSCoreAudioDevice(); void setVolume(float volume); float volume(); void setMuted(bool muted); bool isMuted(); void updateType(); }; static const AudioObjectPropertyAddress kAudioHardwarePropertyAddress = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; static const AudioObjectPropertyAddress kAudioStreamPropertyAddress = { kAudioDevicePropertyStreams, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; static const AudioObjectPropertyAddress kAudioMasterVolumePropertyAddress = { kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; static const AudioObjectPropertyAddress kAudioLeftVolumePropertyAddress = { kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeOutput, 1 }; static const AudioObjectPropertyAddress kAudioRightVolumePropertyAddress = { kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeOutput, 2 }; static const AudioObjectPropertyAddress kAudioMasterMutedPropertyAddress = { kAudioDevicePropertyMute, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; static const AudioObjectPropertyAddress kAudioMasterDataSourcePropertyAddress = { kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; OSStatus onVolumeChanged(AudioObjectID object, UInt32 numAddresses, const AudioObjectPropertyAddress addresses[], void *context) { Q_UNUSED(object); Q_UNUSED(addresses); Q_UNUSED(numAddresses); SystemvolumePlugin *plugin = (SystemvolumePlugin*)context; plugin->updateDeviceVolume(object); return noErr; } OSStatus onMutedChanged(AudioObjectID object, UInt32 numAddresses, const AudioObjectPropertyAddress addresses[], void *context) { Q_UNUSED(object); Q_UNUSED(addresses); Q_UNUSED(numAddresses); SystemvolumePlugin *plugin = (SystemvolumePlugin*)context; plugin->updateDeviceMuted(object); return noErr; } OSStatus onOutputSourceChanged(AudioObjectID object, UInt32 numAddresses, const AudioObjectPropertyAddress addresses[], void *context) { Q_UNUSED(object); Q_UNUSED(addresses); Q_UNUSED(numAddresses); SystemvolumePlugin *plugin = (SystemvolumePlugin*)context; plugin->sendSinkList(); return noErr; } UInt32 getDeviceSourceId(AudioObjectID deviceId) { UInt32 dataSourceId; UInt32 size = sizeof(dataSourceId); OSStatus result = AudioObjectGetPropertyData(deviceId, &kAudioMasterDataSourcePropertyAddress, 0, NULL, &size, &dataSourceId); if (result != noErr) return kAudioDeviceUnknown; return dataSourceId; } QString translateDeviceSource(AudioObjectID deviceId) { UInt32 sourceId = getDeviceSourceId(deviceId); if (sourceId == kAudioDeviceUnknown) { qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unknown data source id of device" << deviceId; return QStringLiteral(""); } CFStringRef sourceName = nullptr; AudioValueTranslation translation; translation.mInputData = &sourceId; translation.mInputDataSize = sizeof(sourceId); translation.mOutputData = &sourceName; translation.mOutputDataSize = sizeof(sourceName); UInt32 translationSize = sizeof(AudioValueTranslation); AudioObjectPropertyAddress propertyAddress = { kAudioDevicePropertyDataSourceNameForIDCFString, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster}; OSStatus result = AudioObjectGetPropertyData(deviceId, &propertyAddress, 0, NULL, &translationSize, &translation); if (result != noErr) { qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Cannot get description of device" << deviceId; return QStringLiteral(""); } QString ret = QString::fromCFString(sourceName); CFRelease(sourceName); return ret; } std::vector GetAllOutputAudioDeviceIDs() { std::vector outputDeviceIds; UInt32 size = 0; OSStatus result; result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &kAudioHardwarePropertyAddress, 0, NULL, &size); if (result != noErr) { qCDebug(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Failed to read size of property " << kAudioHardwarePropertyDevices << " for device/object " << kAudioObjectSystemObject; return {}; } if (size == 0) return {}; size_t deviceCount = size / sizeof(AudioObjectID); std::vector deviceIds(deviceCount); result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &kAudioHardwarePropertyAddress, 0, NULL, &size, deviceIds.data()); if (result != noErr) { qCDebug(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Failed to read object IDs from property " << kAudioHardwarePropertyDevices << " for device/object " << kAudioObjectSystemObject; return {}; } for (AudioDeviceID deviceId : deviceIds) { UInt32 streamCount = 0; result = AudioObjectGetPropertyDataSize(deviceId, &kAudioStreamPropertyAddress, 0, NULL, &streamCount); if (result == noErr && streamCount > 0) { outputDeviceIds.push_back(deviceId); qCDebug(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Device" << deviceId << "added"; } else { qCDebug(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Device" << deviceId << "dropped"; } } return outputDeviceIds; } SystemvolumePlugin::SystemvolumePlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args), m_sinksMap() {} bool SystemvolumePlugin::receivePacket(const NetworkPacket& np) { if (np.has(QStringLiteral("requestSinks"))) { sendSinkList(); } else { QString name = np.get(QStringLiteral("name")); if (m_sinksMap.contains(name)) { if (np.has(QStringLiteral("volume"))) { m_sinksMap[name]->setVolume(np.get(QStringLiteral("volume")) / 100.0); } if (np.has(QStringLiteral("muted"))) { m_sinksMap[name]->setMuted(np.get(QStringLiteral("muted"))); } } } return true; } void SystemvolumePlugin::sendSinkList() { QJsonDocument document; QJsonArray array; if (!m_sinksMap.empty()) { for (MacOSCoreAudioDevice *sink : m_sinksMap) { delete sink; } m_sinksMap.clear(); } std::vector deviceIds = GetAllOutputAudioDeviceIDs(); for (AudioDeviceID deviceId : deviceIds) { MacOSCoreAudioDevice *audioDevice = new MacOSCoreAudioDevice(deviceId); audioDevice->m_description = translateDeviceSource(deviceId); m_sinksMap.insert(QStringLiteral("default-") + QString::number(deviceId), audioDevice); // Add volume change listener AudioObjectAddPropertyListener(deviceId, &kAudioMasterVolumePropertyAddress, &onVolumeChanged, (void *)this); AudioObjectAddPropertyListener(deviceId, &kAudioLeftVolumePropertyAddress, &onVolumeChanged, (void *)this); AudioObjectAddPropertyListener(deviceId, &kAudioRightVolumePropertyAddress, &onVolumeChanged, (void *)this); // Add muted change listener AudioObjectAddPropertyListener(deviceId, &kAudioMasterMutedPropertyAddress, &onMutedChanged, (void *)this); // Add data source change listerner AudioObjectAddPropertyListener(deviceId, &kAudioMasterDataSourcePropertyAddress, &onOutputSourceChanged, (void *)this); QJsonObject sinkObject { {QStringLiteral("name"), QStringLiteral("default-") + QString::number(deviceId)}, {QStringLiteral("muted"), audioDevice->isMuted()}, {QStringLiteral("description"), audioDevice->m_description}, {QStringLiteral("volume"), audioDevice->volume() * 100}, {QStringLiteral("maxVolume"), 100} }; array.append(sinkObject); } document.setArray(array); NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME); np.set(QStringLiteral("sinkList"), document); sendPacket(np); } void SystemvolumePlugin::connected() { sendSinkList(); } void SystemvolumePlugin::updateDeviceMuted(AudioDeviceID deviceId) { for (MacOSCoreAudioDevice *sink : m_sinksMap) { if (sink->m_deviceId == deviceId) { NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME); np.set(QStringLiteral("muted"), (bool)(sink->isMuted())); np.set(QStringLiteral("volume"), (int)(sink->volume() * 100)); np.set(QStringLiteral("name"), QStringLiteral("default-") + QString::number(deviceId)); sendPacket(np); return; } } qCDebug(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Device" << deviceId << "not found while update mute state"; } void SystemvolumePlugin::updateDeviceVolume(AudioDeviceID deviceId) { for (MacOSCoreAudioDevice *sink : m_sinksMap) { if (sink->m_deviceId == deviceId) { NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME); np.set(QStringLiteral("volume"), (int)(sink->volume() * 100)); np.set(QStringLiteral("name"), QStringLiteral("default-") + QString::number(deviceId)); sendPacket(np); return; } } qCDebug(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Device" << deviceId << "not found while update volume"; } MacOSCoreAudioDevice::MacOSCoreAudioDevice(AudioDeviceID deviceId) : m_deviceId(deviceId) { updateType(); } MacOSCoreAudioDevice::~MacOSCoreAudioDevice() { // Volume listener AudioObjectRemovePropertyListener(m_deviceId, &kAudioMasterVolumePropertyAddress, &onVolumeChanged, (void *)this); AudioObjectRemovePropertyListener(m_deviceId, &kAudioLeftVolumePropertyAddress, &onVolumeChanged, (void *)this); AudioObjectRemovePropertyListener(m_deviceId, &kAudioRightVolumePropertyAddress, &onVolumeChanged, (void *)this); // Muted listener AudioObjectRemovePropertyListener(m_deviceId, &kAudioMasterMutedPropertyAddress, &onMutedChanged, (void *)this); // Data source listener AudioObjectRemovePropertyListener(m_deviceId, &kAudioMasterDataSourcePropertyAddress, &onOutputSourceChanged, (void *)this); } void MacOSCoreAudioDevice::setVolume(float volume) { OSStatus result; if (m_deviceId == kAudioObjectUnknown) { qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unable to set volume of Unknown Device"; return; } if (m_isStereo) { result = AudioObjectSetPropertyData(m_deviceId, &kAudioLeftVolumePropertyAddress, 0, NULL, sizeof(volume), &volume); result = AudioObjectSetPropertyData(m_deviceId, &kAudioRightVolumePropertyAddress, 0, NULL, sizeof(volume), &volume); } else { result = AudioObjectSetPropertyData(m_deviceId, &kAudioMasterVolumePropertyAddress, 0, NULL, sizeof(volume), &volume); } if (result != noErr) { qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unable to set volume of Device" << m_deviceId << "to" << volume; } } void MacOSCoreAudioDevice::setMuted(bool muted) { if (m_deviceId == kAudioObjectUnknown) { qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unable to mute an Unknown Device"; return; } UInt32 mutedValue = muted ? 1 : 0; OSStatus result = AudioObjectSetPropertyData(m_deviceId, &kAudioMasterMutedPropertyAddress, 0, NULL, sizeof(mutedValue), &mutedValue); if (result != noErr) { qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unable to set muted state of Device" << m_deviceId << "to" << muted; } } float MacOSCoreAudioDevice::volume() { OSStatus result; if (m_deviceId == kAudioObjectUnknown) { qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unable to get volume of Unknown Device"; return 0.0; } float volume = 0.0; UInt32 volumeDataSize = sizeof(volume); if (m_isStereo) { // Try to get steoreo device volume result = AudioObjectGetPropertyData(m_deviceId, &kAudioLeftVolumePropertyAddress, 0, NULL, &volumeDataSize, &volume); } else { // Try to get master volume result = AudioObjectGetPropertyData(m_deviceId, &kAudioMasterVolumePropertyAddress, 0, NULL, &volumeDataSize, &volume); } if (result != noErr) { qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unable to get volume of Device" << m_deviceId; return 0.0; } return volume; } bool MacOSCoreAudioDevice::isMuted() { if (m_deviceId == kAudioObjectUnknown) { qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unable to get muted state of an Unknown Device"; return false; } UInt32 muted = 0; UInt32 muteddataSize = sizeof(muted); AudioObjectGetPropertyData(m_deviceId, &kAudioMasterMutedPropertyAddress, 0, NULL, &muteddataSize, &muted); return muted == 1; } void MacOSCoreAudioDevice::updateType() { // Try to get volume from left channel to check if it's a stereo device float volume = 0.0; UInt32 volumeDataSize = sizeof(volume); OSStatus result = AudioObjectGetPropertyData(m_deviceId, &kAudioLeftVolumePropertyAddress, 0, NULL, &volumeDataSize, &volume); if (result == noErr) { m_isStereo = true; } else { m_isStereo = false; } } #include "systemvolumeplugin-macos.moc" diff --git a/plugins/systemvolume/systemvolumeplugin-pulse.cpp b/plugins/systemvolume/systemvolumeplugin-pulse.cpp index 5c20638e..11ffea23 100644 --- a/plugins/systemvolume/systemvolumeplugin-pulse.cpp +++ b/plugins/systemvolume/systemvolumeplugin-pulse.cpp @@ -1,133 +1,132 @@ /** * Copyright 2017 Nicolas Fella * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "systemvolumeplugin-pulse.h" #include #include -#include #include #include #include #include #include #include #include +#include "plugin_systemvolume_debug.h" K_PLUGIN_CLASS_WITH_JSON(SystemvolumePlugin, "kdeconnect_systemvolume.json") -Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SYSTEMVOLUME, "kdeconnect.plugin.systemvolume") SystemvolumePlugin::SystemvolumePlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , sinksMap() {} bool SystemvolumePlugin::receivePacket(const NetworkPacket& np) { if (!PulseAudioQt::Context::instance()->isValid()) return false; if (np.has(QStringLiteral("requestSinks"))) { sendSinkList(); } else { QString name = np.get(QStringLiteral("name")); PulseAudioQt::Sink *sink = sinksMap.value(name); if (sink) { if (np.has(QStringLiteral("volume"))) { int volume = np.get(QStringLiteral("volume")); sink->setVolume(volume); sink->setMuted(false); } if (np.has(QStringLiteral("muted"))) { sink->setMuted(np.get(QStringLiteral("muted"))); } } } return true; } void SystemvolumePlugin::sendSinkList() { QJsonDocument document; QJsonArray array; sinksMap.clear(); const auto sinks = PulseAudioQt::Context::instance()->sinks(); for (PulseAudioQt::Sink* sink : sinks) { sinksMap.insert(sink->name(), sink); connect(sink, &PulseAudioQt::Sink::volumeChanged, this, [this, sink] { NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME); np.set(QStringLiteral("volume"), sink->volume()); np.set(QStringLiteral("name"), sink->name()); sendPacket(np); }); connect(sink, &PulseAudioQt::Sink::mutedChanged, this, [this, sink] { NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME); np.set(QStringLiteral("muted"), sink->isMuted()); np.set(QStringLiteral("name"), sink->name()); sendPacket(np); }); QJsonObject sinkObject { {QStringLiteral("name"), sink->name()}, {QStringLiteral("muted"), sink->isMuted()}, {QStringLiteral("description"), sink->description()}, {QStringLiteral("volume"), sink->volume()}, {QStringLiteral("maxVolume"), PulseAudioQt::normalVolume()} }; array.append(sinkObject); } document.setArray(array); NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME); np.set(QStringLiteral("sinkList"), document); sendPacket(np); } void SystemvolumePlugin::connected() { connect(PulseAudioQt::Context::instance(), &PulseAudioQt::Context::sinkAdded, this, [this] { sendSinkList(); }); connect(PulseAudioQt::Context::instance(), &PulseAudioQt::Context::sinkRemoved, this, [this] { sendSinkList(); }); const auto sinks = PulseAudioQt::Context::instance()->sinks(); for (PulseAudioQt::Sink* sink : sinks) { sinksMap.insert(sink->name(), sink); } } #include "systemvolumeplugin-pulse.moc" diff --git a/plugins/systemvolume/systemvolumeplugin-win.cpp b/plugins/systemvolume/systemvolumeplugin-win.cpp index a1c7c89e..9e89d299 100644 --- a/plugins/systemvolume/systemvolumeplugin-win.cpp +++ b/plugins/systemvolume/systemvolumeplugin-win.cpp @@ -1,348 +1,347 @@ /** * Copyright 2018 Jun Bo Bi * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "systemvolumeplugin-win.h" #include -#include #include #include #include #include #include #include -K_PLUGIN_CLASS_WITH_JSON(SystemvolumePlugin, "kdeconnect_systemvolume.json") +#include "plugin_systemvolume_debug.h" -Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SYSTEMVOLUME, "kdeconnect.plugin.systemvolume") +K_PLUGIN_CLASS_WITH_JSON(SystemvolumePlugin, "kdeconnect_systemvolume.json") // Private classes of SystemvolumePlugin class SystemvolumePlugin::CMMNotificationClient : public IMMNotificationClient { public: CMMNotificationClient(SystemvolumePlugin &x) : enclosing(x), _cRef(1){}; ~CMMNotificationClient(){}; // IUnknown methods -- AddRef, Release, and QueryInterface ULONG STDMETHODCALLTYPE AddRef() override { return InterlockedIncrement(&_cRef); } ULONG STDMETHODCALLTYPE Release() override { ULONG ulRef = InterlockedDecrement(&_cRef); if (ulRef == 0) { delete this; } return ulRef; } HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID **ppvInterface) override { if (IID_IUnknown == riid) { AddRef(); *ppvInterface = (IUnknown *)this; } else if (__uuidof(IMMNotificationClient) == riid) { AddRef(); *ppvInterface = (IMMNotificationClient *)this; } else { *ppvInterface = NULL; return E_NOINTERFACE; } return S_OK; } // Callback methods for device-event notifications. HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId) override { if (flow == eRender) { enclosing.sendSinkList(); } return S_OK; } HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId) override { enclosing.sendSinkList(); return S_OK; } HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId) override { enclosing.sendSinkList(); return S_OK; } HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) override { enclosing.sendSinkList(); return S_OK; } HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) override { enclosing.sendSinkList(); return S_OK; } private: LONG _cRef; SystemvolumePlugin &enclosing; }; class SystemvolumePlugin::CAudioEndpointVolumeCallback : public IAudioEndpointVolumeCallback { LONG _cRef; public: CAudioEndpointVolumeCallback(SystemvolumePlugin &x, QString sinkName) : enclosing(x), name(sinkName), _cRef(1) {} ~CAudioEndpointVolumeCallback(){}; // IUnknown methods -- AddRef, Release, and QueryInterface ULONG STDMETHODCALLTYPE AddRef() override { return InterlockedIncrement(&_cRef); } ULONG STDMETHODCALLTYPE Release() override { ULONG ulRef = InterlockedDecrement(&_cRef); if (ulRef == 0) { delete this; } return ulRef; } HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID **ppvInterface) override { if (IID_IUnknown == riid) { AddRef(); *ppvInterface = (IUnknown *)this; } else if (__uuidof(IMMNotificationClient) == riid) { AddRef(); *ppvInterface = (IMMNotificationClient *)this; } else { *ppvInterface = NULL; return E_NOINTERFACE; } return S_OK; } // Callback method for endpoint-volume-change notifications. HRESULT STDMETHODCALLTYPE OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) override { NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME); np.set(QStringLiteral("volume"), (int)(pNotify->fMasterVolume * 100)); np.set(QStringLiteral("muted"), pNotify->bMuted); np.set(QStringLiteral("name"), name); enclosing.sendPacket(np); return S_OK; } private: SystemvolumePlugin &enclosing; QString name; }; SystemvolumePlugin::SystemvolumePlugin(QObject *parent, const QVariantList &args) : KdeConnectPlugin(parent, args), sinkList() { CoInitialize(nullptr); deviceEnumerator = nullptr; HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (LPVOID *)&(deviceEnumerator)); valid = (hr == S_OK); if (!valid) { qWarning("Initialization failed: Failed to create MMDeviceEnumerator"); qWarning("Error Code: %lx", hr); } } SystemvolumePlugin::~SystemvolumePlugin() { if (valid) { deviceEnumerator->UnregisterEndpointNotificationCallback(deviceCallback); deviceEnumerator->Release(); deviceEnumerator = nullptr; } } bool SystemvolumePlugin::sendSinkList() { if (!valid) return false; QJsonDocument document; QJsonArray array; HRESULT hr; if (!sinkList.empty()) { for (auto const &sink : sinkList) { sink.first->UnregisterControlChangeNotify(sink.second); sink.first->Release(); sink.second->Release(); } sinkList.clear(); } IMMDeviceCollection *devices = nullptr; hr = deviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &devices); if (hr != S_OK) { qWarning("Failed to Enumumerate AudioEndpoints"); qWarning("Error Code: %lx", hr); return false; } unsigned int deviceCount; devices->GetCount(&deviceCount); for (unsigned int i = 0; i < deviceCount; i++) { IMMDevice *device = nullptr; IPropertyStore *deviceProperties = nullptr; PROPVARIANT deviceProperty; QString name; QString desc; float volume; BOOL muted; IAudioEndpointVolume *endpoint = nullptr; CAudioEndpointVolumeCallback *callback; // Get Properties devices->Item(i, &device); device->OpenPropertyStore(STGM_READ, &deviceProperties); deviceProperties->GetValue(PKEY_Device_FriendlyName, &deviceProperty); name = QString::fromWCharArray(deviceProperty.pwszVal); //PropVariantClear(&deviceProperty); #ifndef __MINGW32__ deviceProperties->GetValue(PKEY_Device_DeviceDesc, &deviceProperty); desc = QString::fromWCharArray(deviceProperty.pwszVal); //PropVariantClear(&deviceProperty); #endif QJsonObject sinkObject; sinkObject.insert(QStringLiteral("name"), name); sinkObject.insert(QStringLiteral("description"), desc); hr = device->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_ALL, NULL, (void **)&endpoint); if (hr != S_OK) { qWarning() << "Failed to create IAudioEndpointVolume for device:" << name; qWarning("Error Code: %lx", hr); device->Release(); continue; } endpoint->GetMasterVolumeLevelScalar(&volume); endpoint->GetMute(&muted); sinkObject.insert(QStringLiteral("muted"), (bool)muted); sinkObject.insert(QStringLiteral("volume"), (qint64)(volume * 100)); sinkObject.insert(QStringLiteral("maxVolume"), (qint64)100); // Register Callback callback = new CAudioEndpointVolumeCallback(*this, name); sinkList[name] = qMakePair(endpoint, callback); endpoint->RegisterControlChangeNotify(callback); device->Release(); array.append(sinkObject); } devices->Release(); document.setArray(array); NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME); np.set(QStringLiteral("sinkList"), document); sendPacket(np); return true; } void SystemvolumePlugin::connected() { if (!valid) return; deviceCallback = new CMMNotificationClient(*this); deviceEnumerator->RegisterEndpointNotificationCallback(deviceCallback); sendSinkList(); } bool SystemvolumePlugin::receivePacket(const NetworkPacket &np) { if (!valid) return false; if (np.has(QStringLiteral("requestSinks"))) { return sendSinkList(); } else { QString name = np.get(QStringLiteral("name")); if (sinkList.contains(name)) { if (np.has(QStringLiteral("volume"))) { sinkList[name].first->SetMasterVolumeLevelScalar((float)np.get(QStringLiteral("volume")) / 100, NULL); } if (np.has(QStringLiteral("muted"))) { sinkList[name].first->SetMute(np.get(QStringLiteral("muted")), NULL); } } } return true; } #include "systemvolumeplugin-win.moc" diff --git a/plugins/telephony/CMakeLists.txt b/plugins/telephony/CMakeLists.txt index 4eb34fa1..bb616d47 100644 --- a/plugins/telephony/CMakeLists.txt +++ b/plugins/telephony/CMakeLists.txt @@ -1,14 +1,22 @@ +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER plugin_telephony_debug.h + IDENTIFIER KDECONNECT_PLUGIN_TELEPHONY CATEGORY_NAME kdeconnect.plugin.telephony + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (plugin telephony)") + set(kdeconnect_telephony_SRCS telephonyplugin.cpp + ${debug_file_SRCS} ) include_directories(${CMAKE_BINARY_DIR}) kdeconnect_add_plugin(kdeconnect_telephony JSON kdeconnect_telephony.json SOURCES ${kdeconnect_telephony_SRCS}) target_link_libraries(kdeconnect_telephony kdeconnectcore KF5::I18n KF5::Notifications Qt5::DBus ) diff --git a/plugins/telephony/telephonyplugin.cpp b/plugins/telephony/telephonyplugin.cpp index f84de9a2..b6a0f215 100644 --- a/plugins/telephony/telephonyplugin.cpp +++ b/plugins/telephony/telephonyplugin.cpp @@ -1,121 +1,120 @@ /** * Copyright 2013 Albert Vaca * Copyright 2018 Simon Redman * * 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 #include #include #include #include +#include "plugin_telephony_debug.h" K_PLUGIN_CLASS_WITH_JSON(TelephonyPlugin, "kdeconnect_telephony.json") -Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_TELEPHONY, "kdeconnect.plugin.telephony") - TelephonyPlugin::TelephonyPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) { } void TelephonyPlugin::createNotification(const NetworkPacket& np) { 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"), "")); QString content, type, icon; if (event == QLatin1String("ringing")) { type = QStringLiteral("callReceived"); icon = QStringLiteral("call-start"); 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", contactName); } else if (event == QLatin1String("talking")) { if (m_currentCallNotification) { m_currentCallNotification->close(); } return; } Q_EMIT callReceived(type, phoneNumber, contactName); qCDebug(KDECONNECT_PLUGIN_TELEPHONY) << "Creating notification with type:" << type; if (!m_currentCallNotification) { m_currentCallNotification = new KNotification(type, KNotification::Persistent, this); } if (!phoneThumbnail.isEmpty()) { QPixmap photo; photo.loadFromData(phoneThumbnail, "JPEG"); m_currentCallNotification->setPixmap(photo); } else { m_currentCallNotification->setIconName(icon); } m_currentCallNotification->setComponentName(QStringLiteral("kdeconnect")); m_currentCallNotification->setTitle(device()->name()); m_currentCallNotification->setText(content); if (event == QLatin1String("ringing")) { m_currentCallNotification->setActions( QStringList(i18n("Mute Call")) ); connect(m_currentCallNotification, &KNotification::action1Activated, this, &TelephonyPlugin::sendMutePacket); } m_currentCallNotification->sendEvent(); } bool TelephonyPlugin::receivePacket(const NetworkPacket& np) { if (np.get(QStringLiteral("isCancel"))) { if (m_currentCallNotification) { m_currentCallNotification->close(); } return true; } // ignore old style sms packet if (np.get(QStringLiteral("event")) == QLatin1String("sms")) { return false; } createNotification(np); return true; } void TelephonyPlugin::sendMutePacket() { NetworkPacket packet(PACKET_TYPE_TELEPHONY_REQUEST_MUTE, {{QStringLiteral("action"), QStringLiteral("mute")}}); sendPacket(packet); } QString TelephonyPlugin::dbusPath() const { return QStringLiteral("/modules/kdeconnect/devices/") + device()->id() + QStringLiteral("/telephony"); } #include "telephonyplugin.moc" diff --git a/plugins/telephony/telephonyplugin.h b/plugins/telephony/telephonyplugin.h index bb119c61..368dbae0 100644 --- a/plugins/telephony/telephonyplugin.h +++ b/plugins/telephony/telephonyplugin.h @@ -1,82 +1,79 @@ /** * Copyright 2013 Albert Vaca * Copyright 2018 Simon Redman * * 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 /** * Packet used for simple telephony events * * It contains the key "event" which maps to a string indicating the type of event: * - "ringing" - A phone call is incoming * - "missedCall" - An incoming call was not answered * * Historically, "sms" was a valid event, but support for that has been dropped in favour * of the SMS plugin's more expressive interfaces * * Depending on the event, other fields may be defined */ #define PACKET_TYPE_TELEPHONY QStringLiteral("kdeconnect.telephony") #define PACKET_TYPE_TELEPHONY_REQUEST_MUTE QStringLiteral("kdeconnect.telephony.request_mute") -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); ~TelephonyPlugin() = default; bool receivePacket(const NetworkPacket& np) override; void connected() override {} QString dbusPath() const override; public: Q_SIGNALS: Q_SCRIPTABLE void callReceived(const QString& event, const QString& number, const QString contactName); private Q_SLOTS: void sendMutePacket(); private: /** * Create a notification for: * - Incoming call (with the option to mute the ringing) * - Missed call */ void createNotification(const NetworkPacket& np); QPointer m_currentCallNotification; }; #endif diff --git a/smsapp/CMakeLists.txt b/smsapp/CMakeLists.txt index 19f69d88..c1c764e1 100644 --- a/smsapp/CMakeLists.txt +++ b/smsapp/CMakeLists.txt @@ -1,55 +1,77 @@ qt5_add_resources(KCSMS_SRCS resources.qrc) find_package(KF5People) +set(smshelper_debug_file_SRCS) +ecm_qt_declare_logging_category( + smshelper_debug_file_SRCS HEADER smshelper_debug.h + IDENTIFIER KDECONNECT_SMS_SMSHELPER CATEGORY_NAME kdeconnect.sms.smshelper + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (smshelper)") + +set(sms_debug_files_SRCS) +ecm_qt_declare_logging_category( + sms_debug_files_SRCS HEADER sms_conversation_debug.h + IDENTIFIER KDECONNECT_SMS_CONVERSATION_MODEL CATEGORY_NAME kdeconnect.sms.conversation + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (sms conversation model)") + +ecm_qt_declare_logging_category( + sms_debug_files_SRCS HEADER sms_conversations_list_debug.h + IDENTIFIER KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL CATEGORY_NAME kdeconnect.sms.conversations_list + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (sms conversations list)") + add_library(kdeconnectsmshelper smshelper.cpp gsmasciimap.cpp + ${smshelper_debug_file_SRCS} ) set_target_properties(kdeconnectsmshelper PROPERTIES VERSION ${KDECONNECT_VERSION} SOVERSION ${KDECONNECT_VERSION_MAJOR} ) generate_export_header(kdeconnectsmshelper EXPORT_FILE_NAME ${CMAKE_CURRENT_BINARY_DIR}/kdeconnectsms_export.h BASE_NAME KDEConnectSmsAppLib) target_include_directories(kdeconnectsmshelper PUBLIC ${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) target_link_libraries(kdeconnectsmshelper PUBLIC Qt5::Core Qt5::DBus KF5::People Qt5::Qml kdeconnectinterfaces ) # If ever this library is actually used by someone else, we should export these headers set(libkdeconnectsmshelper_HEADERS smshelper.h ${CMAKE_CURRENT_BINARY_DIR}/kdeconnectsms_export.h ) add_executable(kdeconnect-sms main.cpp conversationlistmodel.cpp conversationmodel.cpp conversationssortfilterproxymodel.cpp - ${KCSMS_SRCS}) + ${KCSMS_SRCS} + ${sms_debug_files_SRCS}) target_include_directories(kdeconnect-sms PUBLIC ${CMAKE_BINARY_DIR}) target_link_libraries(kdeconnect-sms kdeconnectsmshelper kdeconnectinterfaces Qt5::Quick Qt5::Widgets KF5::CoreAddons KF5::DBusAddons KF5::I18n KF5::People ) install(TARGETS kdeconnect-sms ${INSTALL_TARGETS_DEFAULT_ARGS}) install(TARGETS kdeconnectsmshelper ${INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY NAMELINK_SKIP) install(PROGRAMS org.kde.kdeconnect.sms.desktop DESTINATION ${KDE_INSTALL_APPDIR}) diff --git a/smsapp/conversationlistmodel.cpp b/smsapp/conversationlistmodel.cpp index f0cb7583..854787d4 100644 --- a/smsapp/conversationlistmodel.cpp +++ b/smsapp/conversationlistmodel.cpp @@ -1,286 +1,284 @@ /** * Copyright (C) 2018 Aleix Pol Gonzalez * Copyright (C) 2018 Simon Redman * * 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 "conversationlistmodel.h" #include -#include #include #include #include "interfaces/conversationmessage.h" #include "interfaces/dbusinterfaces.h" #include "smshelper.h" - -Q_LOGGING_CATEGORY(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL, "kdeconnect.sms.conversations_list") +#include "sms_conversations_list_debug.h" #define INVALID_THREAD_ID -1 #define INVALID_DATE -1 ConversationListModel::ConversationListModel(QObject* parent) : QStandardItemModel(parent) , m_conversationsInterface(nullptr) { //qCDebug(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL) << "Constructing" << this; auto roles = roleNames(); roles.insert(FromMeRole, "fromMe"); roles.insert(SenderRole, "sender"); roles.insert(DateRole, "date"); roles.insert(AddressesRole, "addresses"); roles.insert(ConversationIdRole, "conversationId"); roles.insert(MultitargetRole, "isMultitarget"); setItemRoleNames(roles); ConversationMessage::registerDbusType(); } ConversationListModel::~ConversationListModel() { } void ConversationListModel::setDeviceId(const QString& deviceId) { if (deviceId == m_deviceId) { return; } if (deviceId.isEmpty()) { return; } qCDebug(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL) << "setDeviceId" << deviceId << "of" << this; if (m_conversationsInterface) { disconnect(m_conversationsInterface, SIGNAL(conversationCreated(QDBusVariant)), this, SLOT(handleCreatedConversation(QDBusVariant))); disconnect(m_conversationsInterface, SIGNAL(conversationUpdated(QDBusVariant)), this, SLOT(handleConversationUpdated(QDBusVariant))); delete m_conversationsInterface; m_conversationsInterface = nullptr; } // This method still gets called *with a valid deviceID* when the device is not connected while the component is setting up // Detect that case and don't do anything. DeviceDbusInterface device(deviceId); if (!(device.isValid() && device.isReachable())) { return; } m_deviceId = deviceId; Q_EMIT deviceIdChanged(); m_conversationsInterface = new DeviceConversationsDbusInterface(deviceId, this); connect(m_conversationsInterface, SIGNAL(conversationCreated(QDBusVariant)), this, SLOT(handleCreatedConversation(QDBusVariant))); connect(m_conversationsInterface, SIGNAL(conversationUpdated(QDBusVariant)), this, SLOT(handleConversationUpdated(QDBusVariant))); refresh(); } void ConversationListModel::refresh() { if (m_deviceId.isEmpty()) { qWarning() << "refreshing null device"; return; } prepareConversationsList(); m_conversationsInterface->requestAllConversationThreads(); } void ConversationListModel::prepareConversationsList() { if (!m_conversationsInterface->isValid()) { qCWarning(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL) << "Tried to prepareConversationsList with an invalid interface!"; return; } const QDBusPendingReply validThreadIDsReply = m_conversationsInterface->activeConversations(); setWhenAvailable(validThreadIDsReply, [this](const QVariantList& convs) { clear(); // If we clear before we receive the reply, there might be a (several second) visual gap! for (const QVariant& headMessage : convs) { const QDBusArgument data = headMessage.value(); ConversationMessage message; data >> message; createRowFromMessage(message); } displayContacts(); }, this); } void ConversationListModel::handleCreatedConversation(const QDBusVariant& msg) { const ConversationMessage message = ConversationMessage::fromDBus(msg); createRowFromMessage(message); } void ConversationListModel::handleConversationUpdated(const QDBusVariant& msg) { const ConversationMessage message = ConversationMessage::fromDBus(msg); createRowFromMessage(message); } void ConversationListModel::printDBusError(const QDBusError& error) { qCWarning(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL) << error; } QStandardItem* ConversationListModel::conversationForThreadId(qint32 threadId) { for(int i=0, c=rowCount(); idata(ConversationIdRole) == threadId) return it; } return nullptr; } QStandardItem* ConversationListModel::getConversationForAddress(const QString& address) { for(int i = 0; i < rowCount(); ++i) { const auto& it = item(i, 0); if (!it->data(MultitargetRole).toBool()) { if (SmsHelper::isPhoneNumberMatch(it->data(SenderRole).toString(), address)) { return it; } } } return nullptr; } void ConversationListModel::createRowFromMessage(const ConversationMessage& message) { if (message.type() == -1) { // The Android side currently hacks in -1 if something weird comes up // TODO: Remove this hack when MMS support is implemented return; } /** The address of everyone involved in this conversation, which we should not display (check if they are known contacts first) */ const QList rawAddresses = message.addresses(); if (rawAddresses.isEmpty()) { qWarning() << "no addresses!" << message.body(); return; } bool toadd = false; QStandardItem* item = conversationForThreadId(message.threadID()); //Check if we have a contact with which to associate this message, needed if there is no conversation with the contact and we received a message from them if (!item && !message.isMultitarget()) { item = getConversationForAddress(rawAddresses[0].address()); if (item) { item->setData(message.threadID(), ConversationIdRole); } } if (!item) { toadd = true; item = new QStandardItem(); const QString displayNames = SmsHelper::getTitleForAddresses(rawAddresses); const QIcon displayIcon = SmsHelper::getIconForAddresses(rawAddresses); item->setText(displayNames); item->setIcon(displayIcon); item->setData(message.threadID(), ConversationIdRole); item->setData(rawAddresses[0].address(), SenderRole); } // TODO: Upgrade to support other kinds of media // Get the body that we should display QString displayBody = message.containsTextBody() ? message.body() : i18n("(Unsupported Message Type)"); // Prepend the sender's name if (message.isOutgoing()) { displayBody = i18n("You: %1", displayBody); } else { // If the message is incoming, the sender is the first Address const QString senderAddress = item->data(SenderRole).toString(); const auto sender = SmsHelper::lookupPersonByAddress(senderAddress); const QString senderName = sender == nullptr? senderAddress : SmsHelper::lookupPersonByAddress(senderAddress)->name(); displayBody = i18n("%1: %2", senderName, displayBody); } // Update the message if the data is newer // This will be true if a conversation receives a new message, but false when the user // does something to trigger past conversation history loading bool oldDateExists; const qint64 oldDate = item->data(DateRole).toLongLong(&oldDateExists); if (!oldDateExists || message.date() >= oldDate) { // If there was no old data or incoming data is newer, update the record item->setData(QVariant::fromValue(message.addresses()), AddressesRole); item->setData(message.isOutgoing(), FromMeRole); item->setData(displayBody, Qt::ToolTipRole); item->setData(message.date(), DateRole); item->setData(message.isMultitarget(), MultitargetRole); } if (toadd) appendRow(item); } void ConversationListModel::displayContacts() { const QList> personDataList = SmsHelper::getAllPersons(); for(const auto& person : personDataList) { const QVariantList allPhoneNumbers = person->contactCustomProperty(QStringLiteral("all-phoneNumber")).toList(); for (const QVariant& rawPhoneNumber : allPhoneNumbers) { //check for any duplicate phoneNumber and eliminate it if (!getConversationForAddress(rawPhoneNumber.toString())) { QStandardItem* item = new QStandardItem(); item->setText(person->name()); item->setIcon(person->photo()); QList addresses; addresses.append(ConversationAddress(rawPhoneNumber.toString())); item->setData(QVariant::fromValue(addresses), AddressesRole); const QString displayBody = i18n("%1", rawPhoneNumber.toString()); item->setData(displayBody, Qt::ToolTipRole); item->setData(false, MultitargetRole); item->setData(qint64(INVALID_THREAD_ID), ConversationIdRole); item->setData(qint64(INVALID_DATE), DateRole); item->setData(rawPhoneNumber.toString(), SenderRole); appendRow(item); } } } } void ConversationListModel::createConversationForAddress(const QString& address) { QStandardItem* item = new QStandardItem(); const QString canonicalizedAddress = SmsHelper::canonicalizePhoneNumber(address); item->setText(canonicalizedAddress); QList addresses; addresses.append(ConversationAddress(canonicalizedAddress)); item->setData(QVariant::fromValue(addresses), AddressesRole); QString displayBody = i18n("%1", canonicalizedAddress); item->setData(displayBody, Qt::ToolTipRole); item->setData(false, MultitargetRole); item->setData(qint64(INVALID_THREAD_ID), ConversationIdRole); item->setData(qint64(INVALID_DATE), DateRole); item->setData(address, SenderRole); appendRow(item); } diff --git a/smsapp/conversationlistmodel.h b/smsapp/conversationlistmodel.h index 0a9dc54f..f84f1106 100644 --- a/smsapp/conversationlistmodel.h +++ b/smsapp/conversationlistmodel.h @@ -1,88 +1,85 @@ /** * Copyright (C) 2018 Aleix Pol Gonzalez * Copyright (C) 2018 Simon Redman * * 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 CONVERSATIONLISTMODEL_H #define CONVERSATIONLISTMODEL_H #include -#include #include "interfaces/conversationmessage.h" #include "interfaces/dbusinterfaces.h" -Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL) - class ConversationListModel : public QStandardItemModel { Q_OBJECT Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged) public: ConversationListModel(QObject* parent = nullptr); ~ConversationListModel(); enum Roles { /* Roles which apply while working as a single message */ FromMeRole = Qt::UserRole, SenderRole, // The sender of the message. Undefined if this is an outgoing message DateRole, // The date of this message /* Roles which apply while working as the head of a conversation */ AddressesRole, // The Addresses involved in the conversation ConversationIdRole, // The ThreadID of the conversation MultitargetRole, // Indicate that this conversation is multitarget }; Q_ENUM(Roles) QString deviceId() const { return m_deviceId; } void setDeviceId(const QString &/*deviceId*/); Q_SCRIPTABLE void refresh(); /** * This method creates conversation with an arbitrary address */ Q_INVOKABLE void createConversationForAddress(const QString& address); public Q_SLOTS: void handleCreatedConversation(const QDBusVariant& msg); void handleConversationUpdated(const QDBusVariant& msg); void createRowFromMessage(const ConversationMessage& message); void printDBusError(const QDBusError& error); void displayContacts(); Q_SIGNALS: void deviceIdChanged(); private: /** * Get all conversations currently known by the conversationsInterface, if any */ void prepareConversationsList(); QStandardItem* conversationForThreadId(qint32 threadId); QStandardItem* getConversationForAddress(const QString& address); DeviceConversationsDbusInterface* m_conversationsInterface; QString m_deviceId; }; #endif // CONVERSATIONLISTMODEL_H diff --git a/smsapp/conversationmodel.cpp b/smsapp/conversationmodel.cpp index 18933919..c8bccfa7 100644 --- a/smsapp/conversationmodel.cpp +++ b/smsapp/conversationmodel.cpp @@ -1,202 +1,200 @@ /** * Copyright (C) 2018 Aleix Pol Gonzalez * Copyright (C) 2018 Simon Redman * * 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 "conversationmodel.h" -#include - #include #include "interfaces/conversationmessage.h" #include "smshelper.h" -Q_LOGGING_CATEGORY(KDECONNECT_SMS_CONVERSATION_MODEL, "kdeconnect.sms.conversation") +#include "sms_conversation_debug.h" ConversationModel::ConversationModel(QObject* parent) : QStandardItemModel(parent) , m_conversationsInterface(nullptr) { auto roles = roleNames(); roles.insert(FromMeRole, "fromMe"); roles.insert(DateRole, "date"); roles.insert(SenderRole, "sender"); roles.insert(AvatarRole, "avatar"); setItemRoleNames(roles); } ConversationModel::~ConversationModel() { } qint64 ConversationModel::threadId() const { return m_threadId; } void ConversationModel::setThreadId(const qint64& threadId) { if (m_threadId == threadId) return; m_threadId = threadId; clear(); knownMessageIDs.clear(); if (m_threadId != INVALID_THREAD_ID && !m_deviceId.isEmpty()) { requestMoreMessages(); } } void ConversationModel::setDeviceId(const QString& deviceId) { if (deviceId == m_deviceId) return; qCDebug(KDECONNECT_SMS_CONVERSATION_MODEL) << "setDeviceId" << "of" << this; if (m_conversationsInterface) { disconnect(m_conversationsInterface, SIGNAL(conversationUpdated(QDBusVariant)), this, SLOT(handleConversationUpdate(QDBusVariant))); disconnect(m_conversationsInterface, SIGNAL(conversationLoaded(qint64, quint64)), this, SLOT(handleConversationLoaded(qint64, quint64))); disconnect(m_conversationsInterface, SIGNAL(conversationCreated(QDBusVariant)), this, SLOT(handleConversationCreated(QDBusVariant))); delete m_conversationsInterface; } m_deviceId = deviceId; m_conversationsInterface = new DeviceConversationsDbusInterface(deviceId, this); connect(m_conversationsInterface, SIGNAL(conversationUpdated(QDBusVariant)), this, SLOT(handleConversationUpdate(QDBusVariant))); connect(m_conversationsInterface, SIGNAL(conversationLoaded(qint64, quint64)), this, SLOT(handleConversationLoaded(qint64, quint64))); connect(m_conversationsInterface, SIGNAL(conversationCreated(QDBusVariant)), this, SLOT(handleConversationCreated(QDBusVariant))); connect(this, SIGNAL(sendMessageWithoutConversation(QDBusVariant, QString)), m_conversationsInterface, SLOT(sendWithoutConversation(QDBusVariant, QString))); } void ConversationModel::setAddressList(const QList& addressList) { m_addressList = addressList; } void ConversationModel::sendReplyToConversation(const QString& message) { //qCDebug(KDECONNECT_SMS_CONVERSATION_MODEL) << "Trying to send" << message << "to conversation with ID" << m_threadId; m_conversationsInterface->replyToConversation(m_threadId, message); } void ConversationModel::startNewConversation(const QString& message, const QList& addressList) { QVariant addresses; addresses.setValue(addressList); Q_EMIT sendMessageWithoutConversation(QDBusVariant(addresses), message); } void ConversationModel::requestMoreMessages(const quint32& howMany) { if (m_threadId == INVALID_THREAD_ID) { return; } const auto& numMessages = rowCount(); m_conversationsInterface->requestConversation(m_threadId, numMessages, numMessages + howMany); } void ConversationModel::createRowFromMessage(const ConversationMessage& message, int pos) { if (message.threadID() != m_threadId) { // Because of the asynchronous nature of the current implementation of this model, if the // user clicks quickly between threads or for some other reason a message comes when we're // not expecting it, we should not display it in the wrong place qCDebug(KDECONNECT_SMS_CONVERSATION_MODEL) << "Got a message for a thread" << message.threadID() << "but we are currently viewing" << m_threadId << "Discarding."; return; } if (knownMessageIDs.contains(message.uID())) { qCDebug(KDECONNECT_SMS_CONVERSATION_MODEL) << "Ignoring duplicate message with ID" << message.uID(); return; } // TODO: Upgrade to support other kinds of media // Get the body that we should display QString displayBody = message.containsTextBody() ? message.body() : i18n("(Unsupported Message Type)"); ConversationAddress sender = message.addresses().first(); QString senderName = message.isIncoming() ? SmsHelper::getTitleForAddresses({sender}) : QString(); auto item = new QStandardItem; item->setText(displayBody); item->setData(message.type() == ConversationMessage::MessageTypeSent, FromMeRole); item->setData(message.date(), DateRole); item->setData(senderName, SenderRole); insertRow(pos, item); knownMessageIDs.insert(message.uID()); } void ConversationModel::handleConversationUpdate(const QDBusVariant& msg) { ConversationMessage message = ConversationMessage::fromDBus(msg); if (message.threadID() != m_threadId) { // If a conversation which we are not currently viewing was updated, discard the information qCDebug(KDECONNECT_SMS_CONVERSATION_MODEL) << "Saw update for thread" << message.threadID() << "but we are currently viewing" << m_threadId; return; } createRowFromMessage(message, 0); } void ConversationModel::handleConversationCreated(const QDBusVariant& msg) { ConversationMessage message = ConversationMessage::fromDBus(msg); if (m_threadId == INVALID_THREAD_ID && SmsHelper::isPhoneNumberMatch(m_addressList[0].address(), message.addresses().first().address()) && !message.isMultitarget()) { m_threadId = message.threadID(); createRowFromMessage(message, 0); } } void ConversationModel::handleConversationLoaded(qint64 threadID, quint64 numMessages) { Q_UNUSED(numMessages) if (threadID != m_threadId) { return; } // If we get this flag, it means that the phone will not be responding with any more messages // so we should not be showing a loading indicator Q_EMIT loadingFinished(); } QString ConversationModel::getCharCountInfo(const QString& message) const { SmsCharCount count = SmsHelper::getCharCount(message); if (count.messages > 1) { // Show remaining char count and message count return QString::number(count.remaining) + QLatin1Char('/') + QString::number(count.messages); } if (count.messages == 1 && count.remaining < 10) { // Show only remaining char count return QString::number(count.remaining); } else { // Do not show anything return QString(); } } diff --git a/smsapp/conversationmodel.h b/smsapp/conversationmodel.h index 96a6b98c..14784e3e 100644 --- a/smsapp/conversationmodel.h +++ b/smsapp/conversationmodel.h @@ -1,91 +1,88 @@ /** * Copyright (C) 2018 Aleix Pol Gonzalez * Copyright (C) 2018 Simon Redman * * 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 CONVERSATIONMODEL_H #define CONVERSATIONMODEL_H #include -#include #include #include "interfaces/conversationmessage.h" #include "interfaces/dbusinterfaces.h" -Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_SMS_CONVERSATION_MODEL) - #define INVALID_THREAD_ID -1 class ConversationModel : public QStandardItemModel { Q_OBJECT Q_PROPERTY(qint64 threadId READ threadId WRITE setThreadId) Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId) Q_PROPERTY(QList addressList READ addressList WRITE setAddressList) public: ConversationModel(QObject* parent = nullptr); ~ConversationModel(); enum Roles { FromMeRole = Qt::UserRole, SenderRole, // The sender of the message. Undefined if this is an outgoing message DateRole, AvatarRole, // URI to the avatar of the sender of the message. Undefined if outgoing. }; Q_ENUM(Roles) qint64 threadId() const; void setThreadId(const qint64& threadId); QString deviceId() const { return m_deviceId; } void setDeviceId(const QString &/*deviceId*/); QList addressList() const { return m_addressList; } void setAddressList(const QList& addressList); Q_INVOKABLE void sendReplyToConversation(const QString& message); Q_INVOKABLE void startNewConversation(const QString& message, const QList& addressList); Q_INVOKABLE void requestMoreMessages(const quint32& howMany = 10); Q_INVOKABLE QString getCharCountInfo(const QString& message) const; Q_SIGNALS: void loadingFinished(); void sendMessageWithoutConversation(const QDBusVariant& addressList, const QString& message); private Q_SLOTS: void handleConversationUpdate(const QDBusVariant &message); void handleConversationLoaded(qint64 threadID, quint64 numMessages); void handleConversationCreated(const QDBusVariant &message); private: void createRowFromMessage(const ConversationMessage &message, int pos); DeviceConversationsDbusInterface* m_conversationsInterface; QString m_deviceId; qint64 m_threadId = INVALID_THREAD_ID; QList m_addressList; QSet knownMessageIDs; // Set of known Message uIDs }; #endif // CONVERSATIONMODEL_H diff --git a/smsapp/smshelper.cpp b/smsapp/smshelper.cpp index fab12f11..7b0bca52 100644 --- a/smsapp/smshelper.cpp +++ b/smsapp/smshelper.cpp @@ -1,450 +1,448 @@ /** * Copyright (C) 2019 Simon Redman * * 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 "smshelper.h" #include #include #include #include #include #include -#include #include #include #include #include #include "interfaces/conversationmessage.h" #include "smsapp/gsmasciimap.h" - -Q_LOGGING_CATEGORY(KDECONNECT_SMS_SMSHELPER, "kdeconnect.sms.smshelper") +#include "smshelper_debug.h" QObject* SmsHelper::singletonProvider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine); Q_UNUSED(scriptEngine); return new SmsHelper(); } bool SmsHelper::isPhoneNumberMatchCanonicalized(const QString& canonicalPhone1, const QString& canonicalPhone2) { if (canonicalPhone1.isEmpty() || canonicalPhone2.isEmpty()) { // The empty string is not a valid phone number so does not match anything return false; } // To decide if a phone number matches: // 1. Are they similar lengths? If two numbers are very different, probably one is junk data and should be ignored // 2. Is one a superset of the other? Phone number digits get more specific the further towards the end of the string, // so if one phone number ends with the other, it is probably just a more-complete version of the same thing const QString& longerNumber = canonicalPhone1.length() >= canonicalPhone2.length() ? canonicalPhone1 : canonicalPhone2; const QString& shorterNumber = canonicalPhone1.length() < canonicalPhone2.length() ? canonicalPhone1 : canonicalPhone2; const CountryCode& country = determineCountryCode(longerNumber); const bool shorterNumberIsShortCode = isShortCode(shorterNumber, country); const bool longerNumberIsShortCode = isShortCode(longerNumber, country); if ((shorterNumberIsShortCode && !longerNumberIsShortCode) || (!shorterNumberIsShortCode && longerNumberIsShortCode)) { // If only one of the numbers is a short code, they clearly do not match return false; } bool matchingPhoneNumber = longerNumber.endsWith(shorterNumber); return matchingPhoneNumber; } bool SmsHelper::isPhoneNumberMatch(const QString& phone1, const QString& phone2) { const QString& canonicalPhone1 = canonicalizePhoneNumber(phone1); const QString& canonicalPhone2 = canonicalizePhoneNumber(phone2); return isPhoneNumberMatchCanonicalized(canonicalPhone1, canonicalPhone2); } bool SmsHelper::isShortCode(const QString& phoneNumber, const SmsHelper::CountryCode& country) { // Regardless of which country this number belongs to, a number of length less than 6 is a "short code" if (phoneNumber.length() <= 6) { return true; } if (country == CountryCode::Australia && phoneNumber.length() == 8 && phoneNumber.startsWith(QStringLiteral("19"))) { return true; } if (country == CountryCode::CzechRepublic && phoneNumber.length() <= 9) { // This entry of the Wikipedia article is fairly poorly written, so it is not clear whether a // short code with length 7 should start with a 9. Leave it like this for now, upgrade as // we get more information return true; } return false; } SmsHelper::CountryCode SmsHelper::determineCountryCode(const QString& canonicalNumber) { // This is going to fall apart if someone has not entered a country code into their contact book // or if Android decides it can't be bothered to report the country code, but probably we will // be fine anyway if (canonicalNumber.startsWith(QStringLiteral("41"))) { return CountryCode::Australia; } if (canonicalNumber.startsWith(QStringLiteral("420"))) { return CountryCode::CzechRepublic; } // The only countries I care about for the current implementation are Australia and CzechRepublic // If we need to deal with further countries, we should probably find a library return CountryCode::Other; } QString SmsHelper::canonicalizePhoneNumber(const QString& phoneNumber) { static const QRegularExpression leadingZeroes(QStringLiteral("^0*")); QString toReturn(phoneNumber); toReturn = toReturn.remove(QStringLiteral(" ")); toReturn = toReturn.remove(QStringLiteral("-")); toReturn = toReturn.remove(QStringLiteral("(")); toReturn = toReturn.remove(QStringLiteral(")")); toReturn = toReturn.remove(QStringLiteral("+")); toReturn = toReturn.remove(leadingZeroes); if (toReturn.isEmpty()) { // If we have stripped away everything, assume this is a special number (and already canonicalized) return phoneNumber; } return toReturn; } bool SmsHelper::isAddressValid(const QString& address) { QString canonicalizedNumber = canonicalizePhoneNumber(address); // This regular expression matches a wide range of international Phone numbers, minimum of 3 digits and maximum upto 15 digits static const QRegularExpression validNumberPattern(QStringLiteral("^(\\d{3,15})$")); if (validNumberPattern.match(canonicalizedNumber).hasMatch()) { return true; } else { static const QRegularExpression emailPattern(QStringLiteral("^[\\w\\.]*@[\\w\\.]*$")); if (emailPattern.match(address).hasMatch()) { return true; } } return false; } class PersonsCache : public QObject { public: PersonsCache() { connect(&m_people, &QAbstractItemModel::rowsRemoved, this, [this] (const QModelIndex &parent, int first, int last) { if (parent.isValid()) return; for (int i=first; i<=last; ++i) { const QString& uri = m_people.get(i, KPeople::PersonsModel::PersonUriRole).toString(); m_personDataCache.remove(uri); } }); } QSharedPointer personAt(int rowIndex) { const QString& uri = m_people.get(rowIndex, KPeople::PersonsModel::PersonUriRole).toString(); auto& person = m_personDataCache[uri]; if (!person) person.reset(new KPeople::PersonData(uri)); return person; } int count() const { return m_people.rowCount(); } private: KPeople::PersonsModel m_people; QHash> m_personDataCache; }; QList> SmsHelper::getAllPersons() { static PersonsCache s_cache; QList> personDataList; for(int rowIndex = 0; rowIndex < s_cache.count(); rowIndex++) { const auto person = s_cache.personAt(rowIndex); personDataList.append(person); } return personDataList; } QSharedPointer SmsHelper::lookupPersonByAddress(const QString& address) { const QString& canonicalAddress = SmsHelper::canonicalizePhoneNumber(address); QList> personDataList = getAllPersons(); for (const auto& person : personDataList) { const QStringList& allEmails = person->allEmails(); for (const QString& email : allEmails) { // Although we are nominally an SMS messaging app, it is possible to send messages to phone numbers using email -> sms bridges if (address == email) { return person; } } // TODO: Either upgrade KPeople with an allPhoneNumbers method const QVariantList allPhoneNumbers = person->contactCustomProperty(QStringLiteral("all-phoneNumber")).toList(); for (const QVariant& rawPhoneNumber : allPhoneNumbers) { const QString& phoneNumber = SmsHelper::canonicalizePhoneNumber(rawPhoneNumber.toString()); bool matchingPhoneNumber = SmsHelper::isPhoneNumberMatchCanonicalized(canonicalAddress, phoneNumber); if (matchingPhoneNumber) { //qCDebug(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL) << "Matched" << address << "to" << person->name(); return person; } } } return nullptr; } QIcon SmsHelper::combineIcons(const QList& icons) { QIcon icon; if (icons.size() == 0) { // We have no icon :( // Once we are using the generic icon from KPeople for unknown contacts, this should never happen } else if (icons.size() == 1) { icon = icons.first(); } else { // Cook an icon by combining the available icons // Barring better information, use the size of the first icon as the size for the final icon QSize size = icons.first().size(); QPixmap canvas(size); canvas.fill(Qt::transparent); QPainter painter(&canvas); QSize halfSize = size / 2; QRect topLeftQuadrant(QPoint(0, 0), halfSize); QRect topRightQuadrant(topLeftQuadrant.topRight(), halfSize); QRect bottomLeftQuadrant(topLeftQuadrant.bottomLeft(), halfSize); QRect bottomRightQuadrant(topLeftQuadrant.bottomRight(), halfSize); if (icons.size() == 2) { painter.drawPixmap(topLeftQuadrant, icons[0]); painter.drawPixmap(bottomRightQuadrant, icons[1]); } else if (icons.size() == 3) { QRect topMiddle(QPoint(halfSize.width() / 2, 0), halfSize); painter.drawPixmap(topMiddle, icons[0]); painter.drawPixmap(bottomLeftQuadrant, icons[1]); painter.drawPixmap(bottomRightQuadrant, icons[2]); } else { // Four or more painter.drawPixmap(topLeftQuadrant, icons[0]); painter.drawPixmap(topRightQuadrant, icons[1]); painter.drawPixmap(bottomLeftQuadrant, icons[2]); painter.drawPixmap(bottomRightQuadrant, icons[3]); } icon = canvas; } return icon; } QString SmsHelper::getTitleForAddresses(const QList& addresses) { QStringList titleParts; for (const ConversationAddress& address : addresses) { const auto personData = SmsHelper::lookupPersonByAddress(address.address()); if (personData) { titleParts.append(personData->name()); } else { titleParts.append(address.address()); } } // It might be nice to alphabetize before combining so that the names don't move around randomly // (based on how the data came to us from Android) return titleParts.join(QLatin1String(", ")); } QIcon SmsHelper::getIconForAddresses(const QList& addresses) { QList icons; for (const ConversationAddress& address : addresses) { const auto personData = SmsHelper::lookupPersonByAddress(address.address()); if (personData) { icons.append(personData->photo()); } else { static QString dummyAvatar = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kf5/kpeople/dummy_avatar.png")); icons.append(QPixmap(dummyAvatar)); } } // It might be nice to alphabetize by contact before combining so that the pictures don't move // around randomly (based on how the data came to us from Android) return combineIcons(icons); } void SmsHelper::copyToClipboard(const QString& text) { QGuiApplication::clipboard()->setText(text); } SmsCharCount SmsHelper::getCharCount(const QString& message) { const int remainingWhenEmpty = 160; const int septetsInSingleSms = 160; const int septetsInSingleConcatSms = 153; const int charsInSingleUcs2Sms = 70; const int charsInSingleConcatUcs2Sms = 67; SmsCharCount count; bool enc7bit = true; // 7-bit is used when true, UCS-2 if false quint32 septets = 0; // GSM encoding character count (characters in extension are counted as 2 chars) int length = message.length(); // Count characters and detect encoding for (int i = 0; i < length; i++) { QChar ch = message[i]; if (isInGsmAlphabet(ch)) { septets++; } else if (isInGsmAlphabetExtension(ch)) { septets += 2; } else { enc7bit = false; break; } } if (length == 0) { count.bitsPerChar = 7; count.octets = 0; count.remaining = remainingWhenEmpty; count.messages = 1; } else if (enc7bit) { count.bitsPerChar = 7; count.octets = (septets * 7 + 6) / 8; if (septets > septetsInSingleSms) { count.messages = (septetsInSingleConcatSms - 1 + septets) / septetsInSingleConcatSms; count.remaining = (septetsInSingleConcatSms * count.messages - septets) % septetsInSingleConcatSms; } else { count.messages = 1; count.remaining = (septetsInSingleSms - septets) % septetsInSingleSms; } } else { count.bitsPerChar = 16; count.octets = length * 2; // QString should be in UTF-16 if (length > charsInSingleUcs2Sms) { count.messages = (charsInSingleConcatUcs2Sms - 1 + length) / charsInSingleConcatUcs2Sms; count.remaining = (charsInSingleConcatUcs2Sms * count.messages - length) % charsInSingleConcatUcs2Sms; } else { count.messages = 1; count.remaining = (charsInSingleUcs2Sms - length) % charsInSingleUcs2Sms; } } return count; } bool SmsHelper::isInGsmAlphabet(const QChar& ch) { wchar_t unicode = ch.unicode(); if ((unicode & ~0x7f) == 0) { // If the character is ASCII // use map return gsm_ascii_map[unicode]; } else { switch (unicode) { case 0xa1: // “¡” case 0xa7: // “§” case 0xbf: // “¿” case 0xa4: // “¤” case 0xa3: // “£” case 0xa5: // “¥” case 0xe0: // “à” case 0xe5: // “å” case 0xc5: // “Å” case 0xe4: // “ä” case 0xc4: // “Ä” case 0xe6: // “æ” case 0xc6: // “Æ” case 0xc7: // “Ç” case 0xe9: // “é” case 0xc9: // “É” case 0xe8: // “è” case 0xec: // “ì” case 0xf1: // “ñ” case 0xd1: // “Ñ” case 0xf2: // “ò” case 0xf5: // “ö” case 0xd6: // “Ö” case 0xf8: // “ø” case 0xd8: // “Ø” case 0xdf: // “ß” case 0xf9: // “ù” case 0xfc: // “ü” case 0xdc: // “Ü” case 0x393: // “Γ” case 0x394: // “Δ” case 0x398: // “Θ” case 0x39b: // “Λ” case 0x39e: // “Ξ” case 0x3a0: // “Π” case 0x3a3: // “Σ” case 0x3a6: // “Φ” case 0x3a8: // “Ψ” case 0x3a9: // “Ω” return true; } } return false; } bool SmsHelper::isInGsmAlphabetExtension(const QChar& ch) { wchar_t unicode = ch.unicode(); switch (unicode) { case '{': case '}': case '|': case '\\': case '^': case '[': case ']': case '~': case 0x20ac: // Euro sign return true; } return false; } diff --git a/smsapp/smshelper.h b/smsapp/smshelper.h index deb7afb0..47e08932 100644 --- a/smsapp/smshelper.h +++ b/smsapp/smshelper.h @@ -1,141 +1,138 @@ /** * Copyright (C) 2019 Simon Redman * * 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 SMSHELPER_H #define SMSHELPER_H #include #include -#include #include #include #include #include "interfaces/conversationmessage.h" #include "kdeconnectsms_export.h" #include "smsapp/smscharcount.h" -Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_SMS_SMSHELPER) - class PersonsCache; class KDECONNECTSMSAPPLIB_EXPORT SmsHelper : public QObject { Q_OBJECT public: SmsHelper() = default; ~SmsHelper() = default; static QObject* singletonProvider(QQmlEngine *engine, QJSEngine *scriptEngine); enum CountryCode { Australia, CzechRepublic, Other, // I only care about a few country codes }; /** * Return true to indicate the two phone numbers should be considered the same, false otherwise */ Q_INVOKABLE static bool isPhoneNumberMatch(const QString& phone1, const QString& phone2); /** * Return true to indicate the two phone numbers should be considered the same, false otherwise * Requires canonicalized phone numbers as inputs */ Q_INVOKABLE static bool isPhoneNumberMatchCanonicalized(const QString& canonicalPhone1, const QString& canonicalPhone2); /** * See inline comments for how short codes are determined * All information from https://en.wikipedia.org/wiki/Short_code */ Q_INVOKABLE static bool isShortCode(const QString& canonicalNumber, const CountryCode& country); /** * Try to guess the country code from the passed number */ static CountryCode determineCountryCode(const QString& canonicalNumber); /** * Simplify a phone number to a known form */ Q_INVOKABLE static QString canonicalizePhoneNumber(const QString& phoneNumber); /** * Get the data for a particular person given their contact address */ Q_INVOKABLE static QSharedPointer lookupPersonByAddress(const QString& address); /** * Make an icon which combines the many icons * * This mimics what Android does: * If there is only one icon, use that one * If there are two icons, put one in the top-left corner and one in the bottom right * If there are three, put one in the middle of the top and the remaining two in the bottom * If there are four or more, put one in each corner (If more than four, some will be left out) */ Q_INVOKABLE static QIcon combineIcons(const QList& icons); /** * Get a combination of all the addresses as a comma-separated list of: * - The KPeople contact's name (if known) * - The address (if the contact is not known) */ Q_INVOKABLE static QString getTitleForAddresses(const QList& addresses); /** * Get a combined icon for all contacts by finding: * - The KPeople contact's icon (if known) * - A generic icon * and then using SmsHelper::combineIcons */ Q_INVOKABLE static QIcon getIconForAddresses(const QList& addresses); /** * Put the specified text into the system clipboard */ Q_INVOKABLE static void copyToClipboard(const QString& text); /** * Get the data for all persons currently stored on device */ static QList> getAllPersons(); /** * Get SMS character count status of SMS. It contains number of remaining characters * in current SMS (automatically selects 7-bit, 8-bit or 16-bit mode), octet count and * number of messages in concatenated SMS. */ Q_INVOKABLE static SmsCharCount getCharCount(const QString& message); /** * Used to validate arbitrary phone number entered by the user */ Q_INVOKABLE static bool isAddressValid(const QString& address); private: static bool isInGsmAlphabet(const QChar& ch); static bool isInGsmAlphabetExtension(const QChar& ch); }; #endif // SMSHELPER_H