Add support for phone side telephony plugin on Linux.

## Summary

Add support for Phone side (like in the Android and SailfishOS apps) to telephony plugin.
this requires ModemManager to work.
Call state notifications are currently supported, more capabilities are possible.

Tested on PinePhone but should probably work on every linux Phone with ModemManager.
to test, pair kdeconnect on your linux phone to desktop, and call your phone.
you should get notification on the desktop, telling you that phone call is incoming.
This commit is contained in:
Yoram Bar-Haim 2022-12-28 01:06:22 +00:00 committed by Simon Redman
parent 17ab101e61
commit 7b1f10d4d5
7 changed files with 262 additions and 0 deletions

View file

@ -27,3 +27,7 @@ Dependencies:
'frameworks/kpackage': '@stable'
'libraries/pulseaudio-qt': '@stable'
'libraries/plasma-wayland-protocols': '@stable'
- 'on': ['Linux']
'require':
'frameworks/modemmanager-qt': '@stable'

View file

@ -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()

View file

@ -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
)

View file

@ -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.

View file

@ -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"
]
}

View file

@ -0,0 +1,135 @@
/**
* SPDX-FileCopyrightText: 2019 Richard Liebscher <richard.liebscher@gmail.com>
* SPDX-FileCopyrightText: 2022 Yoram Bar Haim <bhyoram@protonmail.com>
*
* SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-KDE-Accepted-GPL
*/
#include "mmtelephonyplugin.h"
#include <KLocalizedString>
#include <QDebug>
#include <QLoggingCategory>
#include "plugin_mmtelephony_debug.h"
#include <KPluginFactory>
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<ModemManager::ModemVoice> _voiceInterface(const QSharedPointer<ModemManager::ModemDevice> modemDevice)
{
return modemDevice->interface(ModemManager::ModemDevice::VoiceInterface).objectCast<ModemManager::ModemVoice>();
}
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<QString>(QStringLiteral("event")) == QLatin1String("mute")) {
// TODO: mute code
return true;
}
return true;
}
void MMTelephonyPlugin::onModemAdded(const QString &path)
{
auto modemDevice = ModemManager::findModemDevice(path);
QSharedPointer<ModemManager::ModemVoice> 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"

View file

@ -0,0 +1,57 @@
/**
* SPDX-FileCopyrightText: 2013 Albert Vaca <albertvaka@gmail.com>
* SPDX-FileCopyrightText: 2018 Simon Redman <simon@ergotech.com>
* SPDX-FileCopyrightText: 2019 Richard Liebscher <richard.liebscher@gmail.com>
* SPDX-FileCopyrightText: 2022 Yoram Bar Haim <bhyoram@protonmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#ifndef TELEPHONYPLUGIN_H
#define TELEPHONYPLUGIN_H
#include <KPluginFactory>
#include <ModemManagerQt/Call>
#include <ModemManagerQt/Manager>
#include <ModemManagerQt/Modem>
#include <ModemManagerQt/ModemVoice>
#include <QList>
#include <QMap>
#include <QObject>
#include <QSet>
#include <QVariant>
#include <core/kdeconnectplugin.h>
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