Compare commits

...

3 commits

Author SHA1 Message Date
Albert Vaca Cintora
2170cfe78e Replace libdbus with qdbus 2023-08-11 11:32:58 +02:00
Albert Vaca Cintora
590c81cf84 Fixes from code review 2023-08-10 14:24:30 +02:00
Albert Vaca Cintora
fc6770e3a4 Use libdbus instead of GIO to listen for notifications 2023-08-10 14:24:30 +02:00
4 changed files with 205 additions and 247 deletions

View file

@ -84,7 +84,8 @@ if(UNIX AND NOT APPLE)
find_package(Qt${QT_MAJOR_VERSION} REQUIRED COMPONENTS WaylandClient) find_package(Qt${QT_MAJOR_VERSION} REQUIRED COMPONENTS WaylandClient)
find_package(WaylandProtocols REQUIRED) find_package(WaylandProtocols REQUIRED)
pkg_check_modules(XkbCommon IMPORTED_TARGET xkbcommon) pkg_check_modules(XkbCommon IMPORTED_TARGET xkbcommon)
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) find_package(PkgConfig QUIET REQUIRED)
pkg_check_modules(DBus REQUIRED dbus-1)
endif() endif()
find_package(KF5PeopleVCard) find_package(KF5PeopleVCard)

View file

@ -12,7 +12,11 @@ target_link_libraries(kdeconnect_sendnotifications
Qt::Gui Qt::Gui
KF${QT_MAJOR_VERSION}::IconThemes KF${QT_MAJOR_VERSION}::IconThemes
KF${QT_MAJOR_VERSION}::ConfigCore KF${QT_MAJOR_VERSION}::ConfigCore
PkgConfig::GIO ${DBus_LIBRARIES}
)
target_include_directories(kdeconnect_sendnotifications
SYSTEM PRIVATE ${DBus_INCLUDE_DIRS}
) )
# Config # Config

View file

@ -1,84 +1,98 @@
/** /**
* SPDX-FileCopyrightText: 2015 Holger Kaelberer <holger.k@elberer.de> * 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 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/ */
#include "notificationslistener.h" #include "notificationslistener.h"
#include <unordered_map> #include <QBuffer>
#include <KConfig>
#include <KConfigGroup>
#include <QDBusArgument> #include <QDBusArgument>
#include <QDBusInterface> #include <QFile>
#include <QIODevice>
#include <QImage> #include <QImage>
#include <QScopeGuard> #include <QDBusMessage>
#include <QStandardPaths> #include <QDBusConnection>
#include <QtDebug> #include <QDBusConnectionInterface>
#include <core/kdeconnectplugin.h>
#include <core/kdeconnectpluginconfig.h>
#include <kiconloader.h> #include <kiconloader.h>
#include <kicontheme.h> #include <kicontheme.h>
#include <core/device.h>
#include <core/kdeconnectplugin.h>
#include <dbushelper.h>
#include "notifyingapplication.h" #include "notifyingapplication.h"
#include "plugin_sendnotifications_debug.h" #include "plugin_sendnotifications_debug.h"
#include "sendnotificationsplugin.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) NotificationsListener::NotificationsListener(KdeConnectPlugin *aPlugin)
: QObject(aPlugin) : QObject(aPlugin)
, m_plugin(aPlugin) , m_plugin(aPlugin)
, sessionBus(QDBusConnection::sessionBus())
{ {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
qRegisterMetaTypeStreamOperators<NotifyingApplication>("NotifyingApplication"); qRegisterMetaTypeStreamOperators<NotifyingApplication>("NotifyingApplication");
#endif #endif
GError *error = nullptr; if (!sessionBus.isConnected()) {
m_gdbusConnection = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, &error); qCWarning(KDECONNECT_PLUGIN_SENDNOTIFICATIONS) << "D-Bus connection failed";
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");
return; return;
} }
KConfig config(filePath, KConfig::OpenFlag::SimpleConfig); loadApplications();
KConfigGroup globalgroup(&config, QStringLiteral("Global"));
m_translatedAppName = globalgroup.readEntry(QStringLiteral("Name"), QStringLiteral("KDE Connect")); 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() void NotificationsListener::loadApplications()
@ -87,14 +101,13 @@ void NotificationsListener::loadApplications()
const QVariantList list = m_plugin->config()->getList(QStringLiteral("applications")); const QVariantList list = m_plugin->config()->getList(QStringLiteral("applications"));
for (const auto &a : list) { for (const auto &a : list) {
NotifyingApplication app = a.value<NotifyingApplication>(); NotifyingApplication app = a.value<NotifyingApplication>();
if (!m_applications.contains(app.name)) { if (!m_applications.contains(app.name))
m_applications.insert(app.name, app); 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 &width,
int &height, int &height,
int &rowStride, int &rowStride,
@ -103,58 +116,26 @@ bool NotificationsListener::parseImageDataArgument(GVariant *argument,
bool &hasAlpha, bool &hasAlpha,
QByteArray &imageData) const QByteArray &imageData) const
{ {
if (g_variant_n_children(argument) != 7) { if (!argument.canConvert<QDBusArgument>())
return false; return false;
} const QDBusArgument dbusArg = argument.value<QDBusArgument>();
dbusArg.beginStructure();
g_autoptr(GVariant) variant; dbusArg >> width >> height >> rowStride >> hasAlpha >> bitsPerSample >> channels >> imageData;
dbusArg.endStructure();
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);
return true; 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; int width, height, rowStride, bitsPerSample, channels;
bool hasAlpha; 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); 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 image = image.rgbSwapped(); // RGBA --> ARGB
}
QSharedPointer<QBuffer> buffer = QSharedPointer<QBuffer>(new QBuffer); QSharedPointer<QIODevice> buffer = iconFromQImage(image);
if (!buffer || !buffer->open(QIODevice::WriteOnly) || !image.save(buffer.data(), "PNG")) { if (!buffer) {
qCWarning(KDECONNECT_PLUGIN_SENDNOTIFICATIONS) << "Could not initialize image buffer"; qCWarning(KDECONNECT_PLUGIN_SENDNOTIFICATIONS) << "Could not initialize image buffer";
return QSharedPointer<QIODevice>(); return QSharedPointer<QIODevice>();
} }
@ -183,171 +165,142 @@ QSharedPointer<QIODevice> NotificationsListener::iconForImageData(GVariant *argu
return buffer; 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 int size = KIconLoader::SizeHuge; // use big size to allow for good quality on high-DPI mobile devices
// quality on high-DPI mobile devices QString iconPath = iconName;
QString iconPath = KIconLoader::global()->iconPath(iconName, -size, true); if (!QFile::exists(iconName)) {
if (!iconPath.isEmpty()) { const KIconTheme *iconTheme = KIconLoader::global()->theme();
if (!iconPath.endsWith(QLatin1String(".png")) && KIconLoader::global()->theme()->name() != QLatin1String("hicolor")) { if (iconTheme) {
// try falling back to hicolor theme: iconPath = iconTheme->iconPath(iconName + QLatin1String(".png"), size, KIconLoader::MatchBest);
KIconTheme hicolor(QStringLiteral("hicolor")); if (iconPath.isEmpty()) {
if (hicolor.isValid()) { iconPath = iconTheme->iconPath(iconName + QLatin1String(".svg"), size, KIconLoader::MatchBest);
iconPath = hicolor.iconPath(iconName + QStringLiteral(".png"), size, KIconLoader::MatchBest); if (iconPath.isEmpty()) {
// qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATIONS) << "Found non-png icon in default theme trying fallback to hicolor:" << iconPath; iconPath = iconTheme->iconPath(iconName + QLatin1String(".svgz"), size, KIconLoader::MatchBest);
}
} }
} else {
qCWarning(KDECONNECT_PLUGIN_SENDNOTIFICATIONS) << "KIconLoader has no theme set";
} }
} }
if (iconPath.isEmpty()) {
if (iconPath.endsWith(QLatin1String(".png"))) 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>(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; Q_UNUSED(actions);
if (!msg) {
return msg;
}
const gchar *interface = g_dbus_message_get_interface(msg); // qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATIONS) << "Got notification appName=" << appName << "replacesId=" << replacesId
if (!interface || strcmp(interface, "org.freedesktop.Notifications")) { // << "appIcon=" << appIcon << "summary=" << summary << "body=" << body << "actions=" << actions << "hints=" << hints << "timeout=" << timeout;
// 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;
}
g_autoptr(GVariant) bodyVariant = g_dbus_message_get_body(msg); auto *config = m_plugin->config();
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));
NotifyingApplication app; NotifyingApplication app;
if (!listener->m_applications.contains(appName)) { if (!m_applications.contains(appName)) {
// new application -> add to config // new application -> add to config
app.name = appName; app.name = appName;
app.icon = appIcon; app.icon = appIcon;
app.active = true; app.active = true;
app.blacklistExpression = QRegularExpression(); app.blacklistExpression = QRegularExpression();
listener->m_applications.insert(app.name, app); m_applications.insert(app.name, app);
// update config: // update config
QVariantList list; 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); 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; // qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATIONS) << "Added new application to config:" << app;
} else { } else {
app = listener->m_applications.value(appName); app = m_applications.value(appName);
} }
if (!app.active) { if (!app.active) {
return nullptr; return;
} }
variant = g_variant_get_child_value(bodyVariant, 7); if (timeout > 0 && config->getBool(QStringLiteral("generalPersistent"), false)) {
const auto timeout = g_variant_get_int32(variant); return;
if (timeout > 0 && listener->m_plugin->config()->getBool(QStringLiteral("generalPersistent"), false)) {
return nullptr;
} }
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; int urgency = -1;
if (auto it = hints.find(QStringLiteral("urgency")); it != hints.end()) { auto urgencyHint = hints.find(QStringLiteral("urgency"));
if (g_variant_is_of_type(it->second, G_VARIANT_TYPE_BYTE)) { if (urgencyHint != hints.end()) {
urgency = g_variant_get_byte(it->second); 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); if (summary.isEmpty()) {
QString ticker = QString::fromUtf8(g_variant_get_string(variant, nullptr)); return;
}
variant = g_variant_get_child_value(bodyVariant, 4); const bool includeBody = config->getBool(QStringLiteral("generalIncludeBody"), true);
const QString body = QString::fromUtf8(g_variant_get_string(variant, nullptr));
if (!body.isEmpty() && listener->m_plugin->config()->getBool(QStringLiteral("generalIncludeBody"), true)) { QString ticker = summary;
if (!body.isEmpty() && includeBody) {
ticker += QStringLiteral(": ") + body; ticker += QStringLiteral(": ") + body;
} }
if (app.blacklistExpression.isValid() && !app.blacklistExpression.pattern().isEmpty() && app.blacklistExpression.match(ticker).hasMatch()) { 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; // qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATIONS) << "Sending notification from" << appName << ":" <<ticker << "; appIcon=" << appIcon;
bool silent = false;
static unsigned id = 0;
NetworkPacket np(PACKET_TYPE_NOTIFICATION, NetworkPacket np(PACKET_TYPE_NOTIFICATION,
{{QStringLiteral("id"), QString::number(replacesId > 0 ? replacesId : ++id)}, {
{QStringLiteral("appName"), appName}, {QStringLiteral("id"), QString::number(replacesId > 0 ? replacesId : ++id)},
{QStringLiteral("ticker"), ticker}, {QStringLiteral("appName"), appName},
{QStringLiteral("isClearable"), timeout == 0}}); // KNotifications are persistent if {QStringLiteral("ticker"), ticker},
// timeout == 0, for other notifications {QStringLiteral("isClearable"), timeout == -1},
// clearability is pointless {QStringLiteral("title"), summary},
{QStringLiteral("silent"), silent},
});
// sync any icon data? if (!body.isEmpty() && includeBody) {
if (listener->m_plugin->config()->getBool(QStringLiteral("generalSynchronizeIcons"), true)) { np.set(QStringLiteral("text"), body);
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());
} }
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 m_plugin->sendPacket(np);
<< "summary=" << ticker << "body=" << body << "hints=" << hints.size() << "urgency=" << urgency
<< "timeout=" << timeout;
return nullptr;
} }
#include "moc_notificationslistener.cpp" #include "moc_notificationslistener.cpp"

View file

@ -1,30 +1,32 @@
/** /**
* SPDX-FileCopyrightText: 2015 Holger Kaelberer <holger.k@elberer.de> * 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 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/ */
#pragma once #pragma once
#include <QBuffer> #include <QHash>
#include <QDBusAbstractAdaptor> #include <QIODevice>
#include <QDBusArgument> #include <QSharedPointer>
#include <QFile> #include <QDBusConnection>
#include <core/device.h>
#include <gio/gio.h> #include "notifyingapplication.h"
class KdeConnectPlugin; class KdeConnectPlugin;
class Notification;
struct NotifyingApplication; struct NotifyingApplication;
#define PACKET_TYPE_NOTIFICATION QStringLiteral("kdeconnect.notification")
// TODO: make a singleton, shared for all devices
class NotificationsListener : public QObject class NotificationsListener : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit NotificationsListener(KdeConnectPlugin *aPlugin); explicit NotificationsListener(KdeConnectPlugin *aPlugin);
~NotificationsListener() override;
protected: protected:
KdeConnectPlugin *m_plugin; KdeConnectPlugin *m_plugin;
@ -32,7 +34,7 @@ protected:
// virtual helper function to make testing possible (QDBusArgument can not // virtual helper function to make testing possible (QDBusArgument can not
// be injected without making a DBUS-call): // be injected without making a DBUS-call):
virtual bool parseImageDataArgument(GVariant *argument, virtual bool parseImageDataArgument(const QVariant &argument,
int &width, int &width,
int &height, int &height,
int &rowStride, int &rowStride,
@ -40,18 +42,16 @@ protected:
int &channels, int &channels,
bool &hasAlpha, bool &hasAlpha,
QByteArray &imageData) const; QByteArray &imageData) const;
QSharedPointer<QIODevice> iconForImageData(GVariant *argument) const; QSharedPointer<QIODevice> iconForImageData(const QVariant &argument) const;
static QSharedPointer<QIODevice> iconForIconName(const QString &iconName); QSharedPointer<QIODevice> iconForIconName(const QString &iconName) const;
QSharedPointer<QIODevice> iconFromQImage(const QImage &image) const;
static GDBusMessage *onMessageFiltered(GDBusConnection *connection, GDBusMessage *msg, int incoming, void *parent); void handleNotification(const QString &, uint, const QString &, const QString &, const QString &, const QStringList &, const QVariantMap &, int);
private Q_SLOTS: private Q_SLOTS:
void handleMessage(const QDBusMessage& message);
void loadApplications(); void loadApplications();
private: private:
void setTranslatedAppName(); QDBusConnection sessionBus;
QString m_translatedAppName;
GDBusConnection *m_gdbusConnection = nullptr;
unsigned m_gdbusFilterId = 0;
}; };