diff --git a/.kde-ci.yml b/.kde-ci.yml index d6e56e1d0..aed6522c5 100644 --- a/.kde-ci.yml +++ b/.kde-ci.yml @@ -27,3 +27,7 @@ Dependencies: 'frameworks/kpackage': '@stable' 'libraries/pulseaudio-qt': '@stable' 'libraries/plasma-wayland-protocols': '@stable' + +- 'on': ['Linux'] + 'require': + 'frameworks/modemmanager-qt': '@stable' diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 01abe99dd..dda19398b 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -38,6 +38,10 @@ if(NOT SAILFISHOS) add_subdirectory(screensaver-inhibit) add_subdirectory(virtualmonitor) + if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + add_subdirectory(mmtelephony) + endif() + if(NOT WIN32) add_subdirectory(sendnotifications) endif() diff --git a/plugins/mmtelephony/CMakeLists.txt b/plugins/mmtelephony/CMakeLists.txt new file mode 100644 index 000000000..1fd9afe1e --- /dev/null +++ b/plugins/mmtelephony/CMakeLists.txt @@ -0,0 +1,21 @@ +find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS ModemManagerQt) +set(debug_file_SRCS) +ecm_qt_declare_logging_category( + debug_file_SRCS HEADER plugin_mmtelephony_debug.h + IDENTIFIER KDECONNECT_PLUGIN_MMTELEPHONY CATEGORY_NAME kdeconnect.plugin.mmtelephony + DEFAULT_SEVERITY Warning + EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (plugin mmtelephony)") + +set(kdeconnect_mmtelephony_SRCS + mmtelephonyplugin.cpp + ${debug_file_SRCS} +) + +kdeconnect_add_plugin(kdeconnect_mmtelephony SOURCES ${kdeconnect_mmtelephony_SRCS}) + +target_link_libraries(kdeconnect_mmtelephony + kdeconnectcore + Qt${QT_MAJOR_VERSION}::DBus + KF5::ModemManagerQt + KF5::I18n +) diff --git a/plugins/mmtelephony/README b/plugins/mmtelephony/README new file mode 100644 index 000000000..495f2356e --- /dev/null +++ b/plugins/mmtelephony/README @@ -0,0 +1,13 @@ + +This plugin will display a notification each time a package with type +"kdeconnect.telephony" is received. The type of notification will change +depending on the contents of the field "event" (string). + +Valid contents for "event" are: "ringing", "talking", "missedCall" and "sms". + +If the incoming package contains a "phoneNumber" string field, the notification +will also display it. Note that "phoneNumber" can be a contact name instead +of an actual phone number. + +If the incoming package contains "isCancel" set to true, the package is ignored. + diff --git a/plugins/mmtelephony/kdeconnect_mmtelephony.json b/plugins/mmtelephony/kdeconnect_mmtelephony.json new file mode 100644 index 000000000..b9dc49ffe --- /dev/null +++ b/plugins/mmtelephony/kdeconnect_mmtelephony.json @@ -0,0 +1,28 @@ +{ + "KPlugin": { + "Authors": [ + { + "Email": "bhyoram@protonmail.com", + "Name": "Yoram Bar-Haim" + } + ], + "Description": "Show notifications for incoming calls", + "EnabledByDefault": true, + "Icon": "call-start", + "Id": "kdeconnect_mmtelephony", + "License": "GPL", + "Name": "ModemManager Telephony integration", + "ServiceTypes": [ + "KdeConnect/Plugin" + ], + "Version": "0.1", + "Website": "https://kdeconnect.kde.org/" + }, + "X-KdeConnect-OutgoingPacketType": [ + "kdeconnect.telephony" + ], + "X-KdeConnect-SupportedPacketType": [ + "kdeconnect.telephony", + "kdeconnect.telephony.request_mute" + ] +} diff --git a/plugins/mmtelephony/mmtelephonyplugin.cpp b/plugins/mmtelephony/mmtelephonyplugin.cpp new file mode 100644 index 000000000..9f8833ddb --- /dev/null +++ b/plugins/mmtelephony/mmtelephonyplugin.cpp @@ -0,0 +1,135 @@ +/** + * SPDX-FileCopyrightText: 2019 Richard Liebscher + * SPDX-FileCopyrightText: 2022 Yoram Bar Haim + * + * SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-KDE-Accepted-GPL + */ + +#include "mmtelephonyplugin.h" + +#include +#include +#include + +#include "plugin_mmtelephony_debug.h" +#include + +K_PLUGIN_CLASS_WITH_JSON(MMTelephonyPlugin, "kdeconnect_mmtelephony.json") + +static const QString PACKET_TYPE_TELEPHONY = QStringLiteral("kdeconnect.telephony"); +static const QString PACKET_TYPE_TELEPHONY_REQUEST_MUTE = QStringLiteral("kdeconnect.telephony.request_mute"); + +QSharedPointer _voiceInterface(const QSharedPointer modemDevice) +{ + return modemDevice->interface(ModemManager::ModemDevice::VoiceInterface).objectCast(); +} + +MMTelephonyPlugin::MMTelephonyPlugin(QObject *parent, const QVariantList &args) + : KdeConnectPlugin(parent, args) +{ + connect(ModemManager::notifier(), &ModemManager::Notifier::modemAdded, this, &MMTelephonyPlugin::onModemAdded); +} + +bool MMTelephonyPlugin::receivePacket(const NetworkPacket &np) +{ + if (np.get(QStringLiteral("event")) == QLatin1String("mute")) { + // TODO: mute code + return true; + } + + return true; +} + +void MMTelephonyPlugin::onModemAdded(const QString &path) +{ + auto modemDevice = ModemManager::findModemDevice(path); + QSharedPointer vcm = _voiceInterface(modemDevice); + auto voice = vcm.get(); + connect(voice, &ModemManager::ModemVoice::callAdded, this, [this, voice](const QString &uni) { + auto call = voice->findCall(uni); + onCallAdded(call); + }); + connect(voice, &ModemManager::ModemVoice::callDeleted, this, [this, voice](const QString &uni) { + auto call = voice->findCall(uni); + onCallRemoved(call); + }); +} + +void MMTelephonyPlugin::onModemRemoved(const QString &path) +{ + Q_UNUSED(path); +} + +void MMTelephonyPlugin::onCallAdded(ModemManager::Call::Ptr call) +{ + qCDebug(KDECONNECT_PLUGIN_MMTELEPHONY) << "Call added" << call->number(); + + connect(call.get(), &ModemManager::Call::stateChanged, this, [=](MMCallState newState, MMCallState oldState, MMCallStateReason reason) { + onCallStateChanged(call.get(), newState, oldState, reason); + }); +} + +void MMTelephonyPlugin::onCallRemoved(ModemManager::Call::Ptr call) +{ + qCDebug(KDECONNECT_PLUGIN_MMTELEPHONY) << "Call removed" << call.get()->number(); +} + +QString MMTelephonyPlugin::stateName(MMCallState state) +{ + QString event; + switch (state) { + case MMCallState::MM_CALL_STATE_RINGING_IN: + event = QStringLiteral("ringing"); + break; + case MMCallState::MM_CALL_STATE_ACTIVE: + event = QStringLiteral("talking"); + break; + case MMCallState::MM_CALL_STATE_TERMINATED: + event = QStringLiteral("disconnected"); + break; + case MMCallState::MM_CALL_STATE_UNKNOWN: + default: + event = QStringLiteral("Unknown"); + } + return event; +} + +void MMTelephonyPlugin::onCallStateChanged(ModemManager::Call *call, MMCallState newState, MMCallState oldState, MMCallStateReason reason) +{ + Q_UNUSED(reason); + auto event = stateName(newState); + + qCDebug(KDECONNECT_PLUGIN_MMTELEPHONY) << "Call state changed" << call->uni() << event; + if (newState != MMCallState::MM_CALL_STATE_TERMINATED) + sendMMTelephonyPacket(call, event); + else + sendCancelMMTelephonyPacket(call, stateName(oldState)); +} + +void MMTelephonyPlugin::sendMMTelephonyPacket(ModemManager::Call *call, const QString &state) +{ + QString phoneNumber = call->number(); + + qCDebug(KDECONNECT_PLUGIN_MMTELEPHONY) << "Phone number is" << phoneNumber; + NetworkPacket np{PACKET_TYPE_TELEPHONY, + { + {QStringLiteral("event"), state}, + {QStringLiteral("phoneNumber"), phoneNumber}, + {QStringLiteral("contactName"), phoneNumber}, + }}; + sendPacket(np); +} + +void MMTelephonyPlugin::sendCancelMMTelephonyPacket(ModemManager::Call *call, const QString &lastState) +{ + QString phoneNumber = call->number(); + + NetworkPacket np{PACKET_TYPE_TELEPHONY, + {{QStringLiteral("event"), lastState}, + {QStringLiteral("phoneNumber"), phoneNumber}, + {QStringLiteral("contactName"), phoneNumber}, + {QStringLiteral("isCancel"), true}}}; + sendPacket(np); +} + +#include "mmtelephonyplugin.moc" diff --git a/plugins/mmtelephony/mmtelephonyplugin.h b/plugins/mmtelephony/mmtelephonyplugin.h new file mode 100644 index 000000000..9b148d37f --- /dev/null +++ b/plugins/mmtelephony/mmtelephonyplugin.h @@ -0,0 +1,57 @@ +/** + * SPDX-FileCopyrightText: 2013 Albert Vaca + * SPDX-FileCopyrightText: 2018 Simon Redman + * SPDX-FileCopyrightText: 2019 Richard Liebscher + * SPDX-FileCopyrightText: 2022 Yoram Bar Haim + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +#ifndef TELEPHONYPLUGIN_H +#define TELEPHONYPLUGIN_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +class QDBusPendingCallWatcher; +class QDBusInterface; +class QDBusError; +class QDBusObjectPath; +class QDBusVariant; + +class MMTelephonyPlugin : public KdeConnectPlugin +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.telephony") + +public: + explicit MMTelephonyPlugin(QObject *parent, const QVariantList &args); + ~MMTelephonyPlugin() override = default; + + bool receivePacket(const NetworkPacket &np) override; + void connected() override + { + } + +private: + void onCallAdded(ModemManager::Call::Ptr call); + void onCallRemoved(ModemManager::Call::Ptr call); + void onModemAdded(const QString &path); + void onModemRemoved(const QString &path); + void onCallStateChanged(ModemManager::Call *call, MMCallState newState, MMCallState oldState, MMCallStateReason reason); + void sendMMTelephonyPacket(ModemManager::Call *call, const QString &state); + void sendCancelMMTelephonyPacket(ModemManager::Call *call, const QString &lastState); + static QString stateName(MMCallState state); +}; + +#endif