From 2a23611413adfe472fa9e5e43660d04ed4cce680607a97b579f0fcc29bfd1fdb Mon Sep 17 00:00:00 2001 From: Fabian Vogt Date: Sun, 21 May 2023 12:54:47 +0000 Subject: [PATCH] Accepting request 1088125 from home:fusionfuture:branches:KDE:Applications - Add patch to avoid incorrect notification IDs from the notification plugin (kde#447385): * Use-org-freedesktop-DBus-Monitoring-to-monitor-notifications.patch The patch adds a new dependency, so according to the release policy it can't be backported, but I think it's nice to backport it when it comes to inline message action/reply. OBS-URL: https://build.opensuse.org/request/show/1088125 OBS-URL: https://build.opensuse.org/package/show/KDE:Applications/kdeconnect-kde?expand=0&rev=110 --- ...-Monitoring-to-monitor-notifications.patch | 448 ++++++++++++++++++ kdeconnect-kde.changes | 7 + kdeconnect-kde.spec | 3 + 3 files changed, 458 insertions(+) create mode 100644 Use-org-freedesktop-DBus-Monitoring-to-monitor-notifications.patch diff --git a/Use-org-freedesktop-DBus-Monitoring-to-monitor-notifications.patch b/Use-org-freedesktop-DBus-Monitoring-to-monitor-notifications.patch new file mode 100644 index 0000000..46ce444 --- /dev/null +++ b/Use-org-freedesktop-DBus-Monitoring-to-monitor-notifications.patch @@ -0,0 +1,448 @@ +From 4523ba588276bc5d2bc7f0e11cf39743efeca263 Mon Sep 17 00:00:00 2001 +From: Fushan Wen +Date: Mon, 13 Mar 2023 02:20:12 +0000 +Subject: [PATCH] Use `org.freedesktop.DBus.Monitoring` to monitor + notifications + +Plasma notification widget also uses the interface, and sometimes +notification ids can become out of sync between the two DBus adaptors. + +BUG: 447385 +FIXED-IN: 23.08 +--- + CMakeLists.txt | 1 + + plugins/sendnotifications/CMakeLists.txt | 1 + + .../notificationslistener.cpp | 238 +++++++++++++----- + .../sendnotifications/notificationslistener.h | 17 +- + 4 files changed, 181 insertions(+), 76 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 898130a9d..ed3c4f13b 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -85,6 +85,7 @@ else() + find_package(Qt${QT_MAJOR_VERSION} REQUIRED COMPONENTS WaylandClient) + find_package(WaylandProtocols REQUIRED) + pkg_check_modules(XkbCommon IMPORTED_TARGET xkbcommon) ++ pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + endif() + + find_package(KF5PeopleVCard) +diff --git a/plugins/sendnotifications/CMakeLists.txt b/plugins/sendnotifications/CMakeLists.txt +index 90bcde7af..f994f7f2e 100644 +--- a/plugins/sendnotifications/CMakeLists.txt ++++ b/plugins/sendnotifications/CMakeLists.txt +@@ -30,6 +30,7 @@ target_link_libraries(kdeconnect_sendnotifications + Qt::Gui + KF5::IconThemes + KF5::ConfigCore ++ PkgConfig::GIO + ) + + ####################################### +diff --git a/plugins/sendnotifications/notificationslistener.cpp b/plugins/sendnotifications/notificationslistener.cpp +index f4982b997..8b5894d3d 100644 +--- a/plugins/sendnotifications/notificationslistener.cpp ++++ b/plugins/sendnotifications/notificationslistener.cpp +@@ -5,13 +5,17 @@ + */ + #include "notificationslistener.h" + ++#include ++ + #include + #include + #include + #include + #include ++#include + #include + #include ++ + #include + #include + +@@ -28,20 +32,26 @@ + #include "qtcompat_p.h" + + NotificationsListener::NotificationsListener(KdeConnectPlugin *aPlugin) +- : QDBusAbstractAdaptor(aPlugin) ++ : QObject(aPlugin) + , m_plugin(aPlugin) + { + qRegisterMetaTypeStreamOperators("NotifyingApplication"); + +- bool ret = QDBusConnection::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() << ":" +- << QDBusConnection::sessionBus().lastError(); +- else +- qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Registered notifications listener for device" << m_plugin->device()->name(); ++ 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'"); + +- 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'")); ++ 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(); +@@ -52,10 +62,8 @@ NotificationsListener::NotificationsListener(KdeConnectPlugin *aPlugin) + 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'")); +- QDBusConnection::sessionBus().unregisterObject(QStringLiteral("/org/freedesktop/Notifications")); ++ g_dbus_connection_remove_filter(m_gdbusConnection, m_gdbusFilterId); ++ g_object_unref(m_gdbusConnection); + } + + void NotificationsListener::setTranslatedAppName() +@@ -86,7 +94,7 @@ void NotificationsListener::loadApplications() + // qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Loaded" << applications.size() << " applications"; + } + +-bool NotificationsListener::parseImageDataArgument(const QVariant &argument, ++bool NotificationsListener::parseImageDataArgument(GVariant *argument, + int &width, + int &height, + int &rowStride, +@@ -95,16 +103,58 @@ bool NotificationsListener::parseImageDataArgument(const QVariant &argument, + bool &hasAlpha, + QByteArray &imageData) const + { +- if (!argument.canConvert()) ++ if (g_variant_n_children(argument) != 7) { + return false; +- const QDBusArgument dbusArg = argument.value(); +- dbusArg.beginStructure(); +- dbusArg >> width >> height >> rowStride >> hasAlpha >> bitsPerSample >> channels >> imageData; +- dbusArg.endStructure(); ++ } ++ ++ 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); ++ + return true; + } + +-QSharedPointer NotificationsListener::iconForImageData(const QVariant &argument) const ++QSharedPointer NotificationsListener::iconForImageData(GVariant *argument) const + { + int width, height, rowStride, bitsPerSample, channels; + bool hasAlpha; +@@ -133,7 +183,7 @@ QSharedPointer NotificationsListener::iconForImageData(const QVariant + return buffer; + } + +-QSharedPointer NotificationsListener::iconForIconName(const QString &iconName) const ++QSharedPointer NotificationsListener::iconForIconName(const QString &iconName) + { + int size = KIconLoader::SizeEnormous; // use big size to allow for good + // quality on high-DPI mobile devices +@@ -154,65 +204,110 @@ QSharedPointer NotificationsListener::iconForIconName(const QString & + 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) ++GDBusMessage *NotificationsListener::onMessageFiltered(GDBusConnection *, GDBusMessage *msg, int, void *parent) + { +- static int id = 0; +- Q_UNUSED(actions); ++ static unsigned id = 0; ++ if (!msg) { ++ return msg; ++ } ++ ++ 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_SENDNOTIFICATION) << "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 +- if (appName == m_translatedAppName) +- return 0; ++ auto listener = static_cast(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; +- if (!m_applications.contains(appName)) { ++ if (!listener->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); ++ listener->m_applications.insert(app.name, app); + // update config: + QVariantList list; +- for (const auto &a : qAsConst(m_applications)) ++ for (const auto &a : std::as_const(listener->m_applications)) + list << QVariant::fromValue(a); +- m_plugin->config()->setList(QStringLiteral("applications"), list); ++ listener->m_plugin->config()->setList(QStringLiteral("applications"), list); + // qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Added new application to config:" << app; + } else { +- app = m_applications.value(appName); ++ app = listener->m_applications.value(appName); ++ } ++ ++ if (!app.active) { ++ return nullptr; + } + +- if (!app.active) +- return 0; ++ 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; ++ } ++ ++ variant = g_variant_get_child_value(bodyVariant, 6); ++ std::unordered_map 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); + +- if (timeout > 0 && m_plugin->config()->getBool(QStringLiteral("generalPersistent"), false)) +- return 0; ++ QScopeGuard cleanupHints([&hints] { ++ for (auto &[_, hint] : hints) { ++ g_variant_unref(hint); ++ } ++ }); + + int urgency = -1; +- if (hints.contains(QStringLiteral("urgency"))) { +- bool ok; +- urgency = hints[QStringLiteral("urgency")].toInt(&ok); +- if (!ok) +- 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); ++ } + } +- if (urgency > -1 && urgency < m_plugin->config()->getInt(QStringLiteral("generalUrgency"), 0)) +- return 0; ++ 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)); + +- QString ticker = summary; +- if (!body.isEmpty() && m_plugin->config()->getBool(QStringLiteral("generalIncludeBody"), true)) ++ variant = g_variant_get_child_value(bodyVariant, 4); ++ const QString body = QString::fromUtf8(g_variant_get_string(variant, nullptr)); ++ ++ if (!body.isEmpty() && listener->m_plugin->config()->getBool(QStringLiteral("generalIncludeBody"), true)) { + ticker += QStringLiteral(": ") + body; ++ } ++ ++ if (app.blacklistExpression.isValid() && !app.blacklistExpression.pattern().isEmpty() && app.blacklistExpression.match(ticker).hasMatch()) { ++ return nullptr; ++ } + +- if (app.blacklistExpression.isValid() && !app.blacklistExpression.pattern().isEmpty() && app.blacklistExpression.match(ticker).hasMatch()) +- return 0; ++ variant = g_variant_get_child_value(bodyVariant, 1); ++ const unsigned replacesId = g_variant_get_uint32(variant); + + // qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Sending notification from" << appName << ":" <m_plugin->config()->getBool(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()) ++ 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 (hints.contains(QStringLiteral("icon_data"))) // < 1.1 backward compatibility +- iconSource = iconForImageData(hints[QStringLiteral("icon_data")]); ++ } 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()); + } + +- m_plugin->sendPacket(np); ++ listener->m_plugin->sendPacket(np); ++ ++ qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Got notification appName=" << appName << "replacesId=" << replacesId << "appIcon=" << appIcon ++ << "summary=" << ticker << "body=" << body << "hints=" << hints.size() << "urgency=" << urgency ++ << "timeout=" << timeout; + +- return (replacesId > 0 ? replacesId : id); ++ return nullptr; + } +diff --git a/plugins/sendnotifications/notificationslistener.h b/plugins/sendnotifications/notificationslistener.h +index 38fcd7688..f1d55a453 100644 +--- a/plugins/sendnotifications/notificationslistener.h ++++ b/plugins/sendnotifications/notificationslistener.h +@@ -13,14 +13,15 @@ + #include + #include + ++#include ++ + class KdeConnectPlugin; + class Notification; + struct NotifyingApplication; + +-class NotificationsListener : public QDBusAbstractAdaptor ++class NotificationsListener : public QObject + { + Q_OBJECT +- Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Notifications") + + public: + explicit NotificationsListener(KdeConnectPlugin *aPlugin); +@@ -32,7 +33,7 @@ protected: + + // virtual helper function to make testing possible (QDBusArgument can not + // be injected without making a DBUS-call): +- virtual bool parseImageDataArgument(const QVariant &argument, ++ virtual bool parseImageDataArgument(GVariant *argument, + int &width, + int &height, + int &rowStride, +@@ -40,11 +41,10 @@ protected: + int &channels, + bool &hasAlpha, + QByteArray &imageData) const; +- QSharedPointer iconForImageData(const QVariant &argument) const; +- QSharedPointer iconForIconName(const QString &iconName) const; ++ QSharedPointer iconForImageData(GVariant *argument) const; ++ static QSharedPointer iconForIconName(const QString &iconName); + +-public Q_SLOTS: +- Q_SCRIPTABLE uint Notify(const QString &, uint, const QString &, const QString &, const QString &, const QStringList &, const QVariantMap &, int); ++ static GDBusMessage *onMessageFiltered(GDBusConnection *connection, GDBusMessage *msg, int incoming, void *parent); + + private Q_SLOTS: + void loadApplications(); +@@ -52,6 +52,9 @@ private Q_SLOTS: + private: + void setTranslatedAppName(); + QString m_translatedAppName; ++ ++ GDBusConnection *m_gdbusConnection = nullptr; ++ unsigned m_gdbusFilterId = 0; + }; + + #endif // NOTIFICATIONSLISTENER_H +-- +GitLab + diff --git a/kdeconnect-kde.changes b/kdeconnect-kde.changes index 7bde8d9..976ce2c 100644 --- a/kdeconnect-kde.changes +++ b/kdeconnect-kde.changes @@ -1,3 +1,10 @@ +------------------------------------------------------------------- +Sun May 21 08:58:13 UTC 2023 - Fusion Future + +- Add patch to avoid incorrect notification IDs from the notification + plugin (kde#447385): + * Use-org-freedesktop-DBus-Monitoring-to-monitor-notifications.patch + ------------------------------------------------------------------- Tue May 9 10:46:49 UTC 2023 - Christophe Marin diff --git a/kdeconnect-kde.spec b/kdeconnect-kde.spec index 3241efc..f8977f6 100644 --- a/kdeconnect-kde.spec +++ b/kdeconnect-kde.spec @@ -29,6 +29,8 @@ Source1: https://download.kde.org/stable/release-service/%{version}/src/% Source2: applications.keyring %endif Source100: kdeconnect-kde.SuSEfirewall +#PATCH-FIX-UPSTREAM kde#447385 +Patch0: Use-org-freedesktop-DBus-Monitoring-to-monitor-notifications.patch BuildRequires: cmake >= 3.0 BuildRequires: extra-cmake-modules BuildRequires: kf5-filesystem @@ -61,6 +63,7 @@ BuildRequires: cmake(Qt5QuickControls2) BuildRequires: cmake(Qt5X11Extras) BuildRequires: cmake(Qt5WaylandClient) BuildRequires: pkgconfig(dbus-1) +BuildRequires: pkgconfig(gio-2.0) BuildRequires: pkgconfig(libfakekey) BuildRequires: pkgconfig(x11) BuildRequires: pkgconfig(xkbcommon)