|
|
|
@ -1,84 +1,98 @@
|
|
|
|
|
/**
|
|
|
|
|
* SPDX-FileCopyrightText: 2015 Holger Kaelberer <holger.k@elberer.de>
|
|
|
|
|
* SPDX-FileCopyrightText: 2018 Richard Liebscher <richard.liebscher@gmail.com>
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "notificationslistener.h"
|
|
|
|
|
|
|
|
|
|
#include <unordered_map>
|
|
|
|
|
|
|
|
|
|
#include <KConfig>
|
|
|
|
|
#include <KConfigGroup>
|
|
|
|
|
#include <QBuffer>
|
|
|
|
|
#include <QDBusArgument>
|
|
|
|
|
#include <QDBusInterface>
|
|
|
|
|
#include <QFile>
|
|
|
|
|
#include <QIODevice>
|
|
|
|
|
#include <QImage>
|
|
|
|
|
#include <QScopeGuard>
|
|
|
|
|
#include <QStandardPaths>
|
|
|
|
|
#include <QtDebug>
|
|
|
|
|
#include <QDBusMessage>
|
|
|
|
|
#include <QDBusConnection>
|
|
|
|
|
#include <QDBusConnectionInterface>
|
|
|
|
|
|
|
|
|
|
#include <core/kdeconnectplugin.h>
|
|
|
|
|
#include <core/kdeconnectpluginconfig.h>
|
|
|
|
|
|
|
|
|
|
#include <kiconloader.h>
|
|
|
|
|
#include <kicontheme.h>
|
|
|
|
|
|
|
|
|
|
#include <core/device.h>
|
|
|
|
|
#include <core/kdeconnectplugin.h>
|
|
|
|
|
|
|
|
|
|
#include <dbushelper.h>
|
|
|
|
|
|
|
|
|
|
#include "notifyingapplication.h"
|
|
|
|
|
#include "plugin_sendnotifications_debug.h"
|
|
|
|
|
#include "sendnotificationsplugin.h"
|
|
|
|
|
|
|
|
|
|
void NotificationsListener::handleMessage(const QDBusMessage& message)
|
|
|
|
|
{
|
|
|
|
|
qWarning() << message;
|
|
|
|
|
|
|
|
|
|
if (message.interface() != QStringLiteral("org.freedesktop.Notifications") || message.member() != QStringLiteral("Notify")) {
|
|
|
|
|
qWarning() << "Wrong method";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QString notifySignature = QStringLiteral("susssasa{sv}i");
|
|
|
|
|
|
|
|
|
|
if (message.signature() != notifySignature) {
|
|
|
|
|
qWarning() << "Wrong signature";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QList<QVariant> args = message.arguments();
|
|
|
|
|
if (args.isEmpty()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString appName = args.at(0).toString();
|
|
|
|
|
uint replacesId = args.at(1).toUInt();
|
|
|
|
|
QString appIcon = args.at(2).toString();
|
|
|
|
|
QString summary = args.at(3).toString();
|
|
|
|
|
QString body = args.at(4).toString();
|
|
|
|
|
QStringList actions = args.at(5).toStringList();
|
|
|
|
|
QVariantMap hints = args.at(6).toMap();
|
|
|
|
|
int timeout = args.at(7).toInt();
|
|
|
|
|
|
|
|
|
|
handleNotification(appName, replacesId, appIcon, summary, body, actions, hints, timeout);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NotificationsListener::NotificationsListener(KdeConnectPlugin *aPlugin)
|
|
|
|
|
: QObject(aPlugin)
|
|
|
|
|
, m_plugin(aPlugin)
|
|
|
|
|
, sessionBus(QDBusConnection::sessionBus())
|
|
|
|
|
{
|
|
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
|
|
|
|
qRegisterMetaTypeStreamOperators<NotifyingApplication>("NotifyingApplication");
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
GError *error = nullptr;
|
|
|
|
|
m_gdbusConnection = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, &error);
|
|
|
|
|
g_assert_no_error(error);
|
|
|
|
|
m_gdbusFilterId = g_dbus_connection_add_filter(m_gdbusConnection, NotificationsListener::onMessageFiltered, this, nullptr);
|
|
|
|
|
|
|
|
|
|
g_autoptr(GDBusMessage) msg =
|
|
|
|
|
g_dbus_message_new_method_call("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus.Monitoring", "BecomeMonitor");
|
|
|
|
|
|
|
|
|
|
GVariantBuilder *arrayBuilder = g_variant_builder_new(G_VARIANT_TYPE("as"));
|
|
|
|
|
g_variant_builder_add(arrayBuilder, "s", "interface='org.freedesktop.Notifications'");
|
|
|
|
|
g_variant_builder_add(arrayBuilder, "s", "member='Notify'");
|
|
|
|
|
|
|
|
|
|
g_dbus_message_set_body(msg, g_variant_new("(asu)", arrayBuilder, 0u));
|
|
|
|
|
g_dbus_connection_send_message(m_gdbusConnection, msg, GDBusSendMessageFlags::G_DBUS_SEND_MESSAGE_FLAGS_NONE, nullptr, &error);
|
|
|
|
|
g_assert_no_error(error);
|
|
|
|
|
|
|
|
|
|
setTranslatedAppName();
|
|
|
|
|
loadApplications();
|
|
|
|
|
|
|
|
|
|
connect(m_plugin->config(), &KdeConnectPluginConfig::configChanged, this, &NotificationsListener::loadApplications);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NotificationsListener::~NotificationsListener()
|
|
|
|
|
{
|
|
|
|
|
qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATIONS) << "Destroying NotificationsListener";
|
|
|
|
|
g_dbus_connection_remove_filter(m_gdbusConnection, m_gdbusFilterId);
|
|
|
|
|
g_object_unref(m_gdbusConnection);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NotificationsListener::setTranslatedAppName()
|
|
|
|
|
{
|
|
|
|
|
QString filePath =
|
|
|
|
|
QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("knotifications5/kdeconnect.notifyrc"), QStandardPaths::LocateFile);
|
|
|
|
|
if (filePath.isEmpty()) {
|
|
|
|
|
qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATIONS)
|
|
|
|
|
<< "Couldn't find kdeconnect.notifyrc to hide kdeconnect notifications on the devices. Using default name.";
|
|
|
|
|
m_translatedAppName = QStringLiteral("KDE Connect");
|
|
|
|
|
if (!sessionBus.isConnected()) {
|
|
|
|
|
qCWarning(KDECONNECT_PLUGIN_SENDNOTIFICATIONS) << "D-Bus connection failed";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
KConfig config(filePath, KConfig::OpenFlag::SimpleConfig);
|
|
|
|
|
KConfigGroup globalgroup(&config, QStringLiteral("Global"));
|
|
|
|
|
m_translatedAppName = globalgroup.readEntry(QStringLiteral("Name"), QStringLiteral("KDE Connect"));
|
|
|
|
|
loadApplications();
|
|
|
|
|
|
|
|
|
|
connect(m_plugin->config(), &KdeConnectPluginConfig::configChanged, this, &NotificationsListener::loadApplications);
|
|
|
|
|
|
|
|
|
|
const QString dbusServiceBus = QStringLiteral("org.freedesktop.DBus");
|
|
|
|
|
const QString dbusPathDbus = QStringLiteral("/org/freedesktop/DBus");
|
|
|
|
|
const QString dbusInterfaceMonitoring = QStringLiteral("org.freedesktop.DBus.Monitoring");
|
|
|
|
|
const QString becomeMonitor = QStringLiteral("BecomeMonitor");
|
|
|
|
|
const quint32 flags = 0;
|
|
|
|
|
const QString match = QStringLiteral("interface='org.freedesktop.Notifications',member='Notify'");
|
|
|
|
|
|
|
|
|
|
QObject::connect(sessionBus.interface(), SIGNAL(MessageReceived(QDBusMessage)), this, SLOT(handleMessage(QDBusMessage)));
|
|
|
|
|
|
|
|
|
|
QDBusMessage msg = QDBusMessage::createMethodCall(dbusServiceBus, dbusPathDbus, dbusInterfaceMonitoring, becomeMonitor);
|
|
|
|
|
QStringList matches = {match};
|
|
|
|
|
msg.setArguments(QList<QVariant>{QVariant(matches), QVariant(flags)});
|
|
|
|
|
QDBusMessage reply = sessionBus.call(msg);
|
|
|
|
|
if (reply.type() == QDBusMessage::ErrorMessage) {
|
|
|
|
|
qCWarning(KDECONNECT_PLUGIN_SENDNOTIFICATIONS).noquote() << "Failed to become a DBus monitor."
|
|
|
|
|
<< "No notifictions will be sent. Error:" << reply.errorMessage();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NotificationsListener::loadApplications()
|
|
|
|
@ -87,14 +101,13 @@ void NotificationsListener::loadApplications()
|
|
|
|
|
const QVariantList list = m_plugin->config()->getList(QStringLiteral("applications"));
|
|
|
|
|
for (const auto &a : list) {
|
|
|
|
|
NotifyingApplication app = a.value<NotifyingApplication>();
|
|
|
|
|
if (!m_applications.contains(app.name)) {
|
|
|
|
|
if (!m_applications.contains(app.name))
|
|
|
|
|
m_applications.insert(app.name, app);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATIONS) << "Loaded" << applications.size() << " applications";
|
|
|
|
|
// qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATIONS) << "Loaded" << m_applications.size() << " applications";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool NotificationsListener::parseImageDataArgument(GVariant *argument,
|
|
|
|
|
bool NotificationsListener::parseImageDataArgument(const QVariant &argument,
|
|
|
|
|
int &width,
|
|
|
|
|
int &height,
|
|
|
|
|
int &rowStride,
|
|
|
|
@ -103,58 +116,26 @@ bool NotificationsListener::parseImageDataArgument(GVariant *argument,
|
|
|
|
|
bool &hasAlpha,
|
|
|
|
|
QByteArray &imageData) const
|
|
|
|
|
{
|
|
|
|
|
if (g_variant_n_children(argument) != 7) {
|
|
|
|
|
if (!argument.canConvert<QDBusArgument>())
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_autoptr(GVariant) variant;
|
|
|
|
|
|
|
|
|
|
variant = g_variant_get_child_value(argument, 0);
|
|
|
|
|
if (g_variant_is_of_type(variant, G_VARIANT_TYPE_INT32)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
width = g_variant_get_int32(variant);
|
|
|
|
|
|
|
|
|
|
variant = g_variant_get_child_value(argument, 1);
|
|
|
|
|
if (g_variant_is_of_type(variant, G_VARIANT_TYPE_INT32)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
height = g_variant_get_int32(variant);
|
|
|
|
|
|
|
|
|
|
variant = g_variant_get_child_value(argument, 2);
|
|
|
|
|
if (g_variant_is_of_type(variant, G_VARIANT_TYPE_INT32)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
rowStride = g_variant_get_int32(variant);
|
|
|
|
|
|
|
|
|
|
variant = g_variant_get_child_value(argument, 3);
|
|
|
|
|
if (g_variant_is_of_type(variant, G_VARIANT_TYPE_BOOLEAN)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
hasAlpha = g_variant_get_boolean(variant);
|
|
|
|
|
|
|
|
|
|
variant = g_variant_get_child_value(argument, 4);
|
|
|
|
|
if (g_variant_is_of_type(variant, G_VARIANT_TYPE_INT32)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
bitsPerSample = g_variant_get_int32(variant);
|
|
|
|
|
|
|
|
|
|
variant = g_variant_get_child_value(argument, 5);
|
|
|
|
|
if (g_variant_is_of_type(variant, G_VARIANT_TYPE_INT32)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
channels = g_variant_get_int32(variant);
|
|
|
|
|
|
|
|
|
|
variant = g_variant_get_child_value(argument, 6);
|
|
|
|
|
if (g_variant_is_of_type(variant, G_VARIANT_TYPE_ARRAY)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
imageData = g_variant_get_bytestring(variant);
|
|
|
|
|
|
|
|
|
|
const QDBusArgument dbusArg = argument.value<QDBusArgument>();
|
|
|
|
|
dbusArg.beginStructure();
|
|
|
|
|
dbusArg >> width >> height >> rowStride >> hasAlpha >> bitsPerSample >> channels >> imageData;
|
|
|
|
|
dbusArg.endStructure();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QSharedPointer<QIODevice> NotificationsListener::iconForImageData(GVariant *argument) const
|
|
|
|
|
QSharedPointer<QIODevice> NotificationsListener::iconFromQImage(const QImage &image) const
|
|
|
|
|
{
|
|
|
|
|
QSharedPointer<QBuffer> buffer = QSharedPointer<QBuffer>(new QBuffer);
|
|
|
|
|
if (!buffer->open(QIODevice::WriteOnly) && !image.save(buffer.data(), "PNG")) {
|
|
|
|
|
qCWarning(KDECONNECT_PLUGIN_SENDNOTIFICATIONS) << "Could not initialize image buffer";
|
|
|
|
|
return QSharedPointer<QIODevice>();
|
|
|
|
|
}
|
|
|
|
|
return buffer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QSharedPointer<QIODevice> NotificationsListener::iconForImageData(const QVariant &argument) const
|
|
|
|
|
{
|
|
|
|
|
int width, height, rowStride, bitsPerSample, channels;
|
|
|
|
|
bool hasAlpha;
|
|
|
|
@ -171,11 +152,12 @@ QSharedPointer<QIODevice> NotificationsListener::iconForImageData(GVariant *argu
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QImage image(reinterpret_cast<uchar *>(imageData.data()), width, height, rowStride, hasAlpha ? QImage::Format_ARGB32 : QImage::Format_RGB32);
|
|
|
|
|
if (hasAlpha)
|
|
|
|
|
if (hasAlpha) {
|
|
|
|
|
image = image.rgbSwapped(); // RGBA --> ARGB
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QSharedPointer<QBuffer> buffer = QSharedPointer<QBuffer>(new QBuffer);
|
|
|
|
|
if (!buffer || !buffer->open(QIODevice::WriteOnly) || !image.save(buffer.data(), "PNG")) {
|
|
|
|
|
QSharedPointer<QIODevice> buffer = iconFromQImage(image);
|
|
|
|
|
if (!buffer) {
|
|
|
|
|
qCWarning(KDECONNECT_PLUGIN_SENDNOTIFICATIONS) << "Could not initialize image buffer";
|
|
|
|
|
return QSharedPointer<QIODevice>();
|
|
|
|
|
}
|
|
|
|
@ -183,171 +165,142 @@ QSharedPointer<QIODevice> NotificationsListener::iconForImageData(GVariant *argu
|
|
|
|
|
return buffer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QSharedPointer<QIODevice> NotificationsListener::iconForIconName(const QString &iconName)
|
|
|
|
|
QSharedPointer<QIODevice> 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_SENDNOTIFICATIONS) << "Found non-png icon in default theme trying fallback to hicolor:" << iconPath;
|
|
|
|
|
int size = KIconLoader::SizeHuge; // use big size to allow for good quality on high-DPI mobile devices
|
|
|
|
|
QString iconPath = iconName;
|
|
|
|
|
if (!QFile::exists(iconName)) {
|
|
|
|
|
const KIconTheme *iconTheme = KIconLoader::global()->theme();
|
|
|
|
|
if (iconTheme) {
|
|
|
|
|
iconPath = iconTheme->iconPath(iconName + QLatin1String(".png"), size, KIconLoader::MatchBest);
|
|
|
|
|
if (iconPath.isEmpty()) {
|
|
|
|
|
iconPath = iconTheme->iconPath(iconName + QLatin1String(".svg"), size, KIconLoader::MatchBest);
|
|
|
|
|
if (iconPath.isEmpty()) {
|
|
|
|
|
iconPath = iconTheme->iconPath(iconName + QLatin1String(".svgz"), size, KIconLoader::MatchBest);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
qCWarning(KDECONNECT_PLUGIN_SENDNOTIFICATIONS) << "KIconLoader has no theme set";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (iconPath.endsWith(QLatin1String(".png")))
|
|
|
|
|
if (iconPath.isEmpty()) {
|
|
|
|
|
qCWarning(KDECONNECT_PLUGIN_SENDNOTIFICATIONS) << "Could not find notification icon:" << iconName;
|
|
|
|
|
return QSharedPointer<QIODevice>();
|
|
|
|
|
} else if (iconPath.endsWith(QLatin1String(".png"))) {
|
|
|
|
|
return QSharedPointer<QIODevice>(new QFile(iconPath));
|
|
|
|
|
return QSharedPointer<QIODevice>();
|
|
|
|
|
} else {
|
|
|
|
|
// TODO: cache icons
|
|
|
|
|
return iconFromQImage(QImage(iconPath));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GDBusMessage *NotificationsListener::onMessageFiltered(GDBusConnection *, GDBusMessage *msg, int, void *parent)
|
|
|
|
|
void NotificationsListener::handleNotification(const QString &appName,
|
|
|
|
|
uint replacesId,
|
|
|
|
|
const QString &appIcon,
|
|
|
|
|
const QString &summary,
|
|
|
|
|
const QString &body,
|
|
|
|
|
const QStringList &actions,
|
|
|
|
|
const QVariantMap &hints,
|
|
|
|
|
int timeout)
|
|
|
|
|
{
|
|
|
|
|
static unsigned id = 0;
|
|
|
|
|
if (!msg) {
|
|
|
|
|
return msg;
|
|
|
|
|
}
|
|
|
|
|
Q_UNUSED(actions);
|
|
|
|
|
|
|
|
|
|
const gchar *interface = g_dbus_message_get_interface(msg);
|
|
|
|
|
if (!interface || strcmp(interface, "org.freedesktop.Notifications")) {
|
|
|
|
|
// The first message will be from org.freedesktop.DBus.Monitoring
|
|
|
|
|
return msg;
|
|
|
|
|
}
|
|
|
|
|
const gchar *member = g_dbus_message_get_member(msg);
|
|
|
|
|
if (!member || strcmp(member, "Notify")) {
|
|
|
|
|
// Even if member is set, the monitor will still notify messages from other members.
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
// qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATIONS) << "Got notification appName=" << appName << "replacesId=" << replacesId
|
|
|
|
|
// << "appIcon=" << appIcon << "summary=" << summary << "body=" << body << "actions=" << actions << "hints=" << hints << "timeout=" << timeout;
|
|
|
|
|
|
|
|
|
|
g_autoptr(GVariant) bodyVariant = g_dbus_message_get_body(msg);
|
|
|
|
|
Q_ASSERT(bodyVariant);
|
|
|
|
|
|
|
|
|
|
// Data order and types: https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html
|
|
|
|
|
g_autoptr(GVariant) variant = g_variant_get_child_value(bodyVariant, 0);
|
|
|
|
|
const QString appName = QString::fromUtf8(g_variant_get_string(variant, nullptr));
|
|
|
|
|
// skip our own notifications
|
|
|
|
|
auto listener = static_cast<NotificationsListener *>(parent);
|
|
|
|
|
|
|
|
|
|
if (appName == listener->m_translatedAppName) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
variant = g_variant_get_child_value(bodyVariant, 2);
|
|
|
|
|
const QString appIcon = QString::fromUtf8(g_variant_get_string(variant, nullptr));
|
|
|
|
|
auto *config = m_plugin->config();
|
|
|
|
|
|
|
|
|
|
NotifyingApplication app;
|
|
|
|
|
if (!listener->m_applications.contains(appName)) {
|
|
|
|
|
if (!m_applications.contains(appName)) {
|
|
|
|
|
// new application -> add to config
|
|
|
|
|
app.name = appName;
|
|
|
|
|
app.icon = appIcon;
|
|
|
|
|
app.active = true;
|
|
|
|
|
app.blacklistExpression = QRegularExpression();
|
|
|
|
|
listener->m_applications.insert(app.name, app);
|
|
|
|
|
// update config:
|
|
|
|
|
m_applications.insert(app.name, app);
|
|
|
|
|
// update config
|
|
|
|
|
QVariantList list;
|
|
|
|
|
for (const auto &a : std::as_const(listener->m_applications))
|
|
|
|
|
for (const auto &a : std::as_const(m_applications))
|
|
|
|
|
list << QVariant::fromValue<NotifyingApplication>(a);
|
|
|
|
|
listener->m_plugin->config()->setList(QStringLiteral("applications"), list);
|
|
|
|
|
config->setList(QStringLiteral("applications"), list);
|
|
|
|
|
// qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATIONS) << "Added new application to config:" << app;
|
|
|
|
|
} else {
|
|
|
|
|
app = listener->m_applications.value(appName);
|
|
|
|
|
app = m_applications.value(appName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!app.active) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
variant = g_variant_get_child_value(bodyVariant, 7);
|
|
|
|
|
const auto timeout = g_variant_get_int32(variant);
|
|
|
|
|
if (timeout > 0 && listener->m_plugin->config()->getBool(QStringLiteral("generalPersistent"), false)) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
if (timeout > 0 && config->getBool(QStringLiteral("generalPersistent"), false)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
variant = g_variant_get_child_value(bodyVariant, 6);
|
|
|
|
|
std::unordered_map<QString, GVariant *> hints;
|
|
|
|
|
GVariantIter *iter;
|
|
|
|
|
const gchar *key;
|
|
|
|
|
GVariant *value = nullptr;
|
|
|
|
|
g_variant_get(variant, "a{sv}", &iter);
|
|
|
|
|
while (g_variant_iter_loop(iter, "{sv}", &key, &value)) {
|
|
|
|
|
hints.emplace(QString::fromUtf8(key), value);
|
|
|
|
|
}
|
|
|
|
|
g_variant_iter_free(iter);
|
|
|
|
|
|
|
|
|
|
QScopeGuard cleanupHints([&hints] {
|
|
|
|
|
for (auto &[_, hint] : hints) {
|
|
|
|
|
g_variant_unref(hint);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
int urgency = -1;
|
|
|
|
|
if (auto it = hints.find(QStringLiteral("urgency")); it != hints.end()) {
|
|
|
|
|
if (g_variant_is_of_type(it->second, G_VARIANT_TYPE_BYTE)) {
|
|
|
|
|
urgency = g_variant_get_byte(it->second);
|
|
|
|
|
}
|
|
|
|
|
auto urgencyHint = hints.find(QStringLiteral("urgency"));
|
|
|
|
|
if (urgencyHint != hints.end()) {
|
|
|
|
|
bool ok;
|
|
|
|
|
urgency = urgencyHint->toInt(&ok);
|
|
|
|
|
if (!ok)
|
|
|
|
|
urgency = -1;
|
|
|
|
|
}
|
|
|
|
|
if (urgency > -1 && urgency < config->getInt(QStringLiteral("generalUrgency"), 0)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (urgency > -1 && urgency < listener->m_plugin->config()->getInt(QStringLiteral("generalUrgency"), 0))
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
|
|
variant = g_variant_get_child_value(bodyVariant, 3);
|
|
|
|
|
QString ticker = QString::fromUtf8(g_variant_get_string(variant, nullptr));
|
|
|
|
|
if (summary.isEmpty()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
variant = g_variant_get_child_value(bodyVariant, 4);
|
|
|
|
|
const QString body = QString::fromUtf8(g_variant_get_string(variant, nullptr));
|
|
|
|
|
const bool includeBody = config->getBool(QStringLiteral("generalIncludeBody"), true);
|
|
|
|
|
|
|
|
|
|
if (!body.isEmpty() && listener->m_plugin->config()->getBool(QStringLiteral("generalIncludeBody"), true)) {
|
|
|
|
|
QString ticker = summary;
|
|
|
|
|
if (!body.isEmpty() && includeBody) {
|
|
|
|
|
ticker += QStringLiteral(": ") + body;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (app.blacklistExpression.isValid() && !app.blacklistExpression.pattern().isEmpty() && app.blacklistExpression.match(ticker).hasMatch()) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
variant = g_variant_get_child_value(bodyVariant, 1);
|
|
|
|
|
const unsigned replacesId = g_variant_get_uint32(variant);
|
|
|
|
|
|
|
|
|
|
// qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATIONS) << "Sending notification from" << appName << ":" <<ticker << "; appIcon=" << appIcon;
|
|
|
|
|
|
|
|
|
|
bool silent = false;
|
|
|
|
|
static unsigned id = 0;
|
|
|
|
|
NetworkPacket np(PACKET_TYPE_NOTIFICATION,
|
|
|
|
|
{{QStringLiteral("id"), QString::number(replacesId > 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
|
|
|
|
|
{
|
|
|
|
|
{QStringLiteral("id"), QString::number(replacesId > 0 ? replacesId : ++id)},
|
|
|
|
|
{QStringLiteral("appName"), appName},
|
|
|
|
|
{QStringLiteral("ticker"), ticker},
|
|
|
|
|
{QStringLiteral("isClearable"), timeout == -1},
|
|
|
|
|
{QStringLiteral("title"), summary},
|
|
|
|
|
{QStringLiteral("silent"), silent},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// sync any icon data?
|
|
|
|
|
if (listener->m_plugin->config()->getBool(QStringLiteral("generalSynchronizeIcons"), true)) {
|
|
|
|
|
QSharedPointer<QIODevice> iconSource;
|
|
|
|
|
// try different image sources according to priorities in notifications-
|
|
|
|
|
// spec version 1.2:
|
|
|
|
|
if (auto it = hints.find(QStringLiteral("image-data")); it != hints.end()) {
|
|
|
|
|
iconSource = listener->iconForImageData(it->second);
|
|
|
|
|
} else if (auto it = hints.find(QStringLiteral("image_data")); it != hints.end()) { // 1.1 backward compatibility
|
|
|
|
|
iconSource = listener->iconForImageData(it->second);
|
|
|
|
|
} else if (auto it = hints.find(QStringLiteral("image-path")); it != hints.end()) {
|
|
|
|
|
iconSource = iconForIconName(QString::fromUtf8(g_variant_get_string(it->second, nullptr)));
|
|
|
|
|
} else if (auto it = hints.find(QStringLiteral("image_path")); it != hints.end()) { // 1.1 backward compatibility
|
|
|
|
|
iconSource = iconForIconName(QString::fromUtf8(g_variant_get_string(it->second, nullptr)));
|
|
|
|
|
} else if (!appIcon.isEmpty()) {
|
|
|
|
|
iconSource = iconForIconName(appIcon);
|
|
|
|
|
} else if (auto it = hints.find(QStringLiteral("icon_data")); it != hints.end()) { // < 1.1 backward compatibility
|
|
|
|
|
iconSource = listener->iconForImageData(it->second);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (iconSource)
|
|
|
|
|
np.setPayload(iconSource, iconSource->size());
|
|
|
|
|
if (!body.isEmpty() && includeBody) {
|
|
|
|
|
np.set(QStringLiteral("text"), body);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
listener->m_plugin->sendPacket(np);
|
|
|
|
|
// Only send icon on first notify (replacesId == 0)
|
|
|
|
|
if (config->getBool(QStringLiteral("generalSynchronizeIcons"), true) && replacesId == 0) {
|
|
|
|
|
QSharedPointer<QIODevice> iconSource;
|
|
|
|
|
// try different image sources according to priorities in notifications-spec version 1.2:
|
|
|
|
|
auto it = hints.find(QStringLiteral("image-data"));
|
|
|
|
|
if (it != hints.end() || (it = hints.find(QStringLiteral("image_data"))) != hints.end()) {
|
|
|
|
|
iconSource = iconForImageData(it.value());
|
|
|
|
|
} else if ((it = hints.find(QStringLiteral("image-path"))) != hints.end() || (it = hints.find(QStringLiteral("image_path"))) != hints.end()) {
|
|
|
|
|
iconSource = iconForIconName(it.value().toString());
|
|
|
|
|
} else if (!appIcon.isEmpty()) {
|
|
|
|
|
iconSource = iconForIconName(appIcon);
|
|
|
|
|
} else if ((it = hints.find(QStringLiteral("icon_data"))) != hints.end()) {
|
|
|
|
|
iconSource = iconForImageData(it.value());
|
|
|
|
|
}
|
|
|
|
|
if (iconSource) {
|
|
|
|
|
np.setPayload(iconSource, iconSource->size());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATIONS) << "Got notification appName=" << appName << "replacesId=" << replacesId << "appIcon=" << appIcon
|
|
|
|
|
<< "summary=" << ticker << "body=" << body << "hints=" << hints.size() << "urgency=" << urgency
|
|
|
|
|
<< "timeout=" << timeout;
|
|
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
m_plugin->sendPacket(np);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#include "moc_notificationslistener.cpp"
|
|
|
|
|