diff --git a/src/imageconverter.h b/src/imageconverter.h index 7f12ce4..d520177 100644 --- a/src/imageconverter.h +++ b/src/imageconverter.h @@ -1,34 +1,34 @@ /* Copyright (C) 2009 Canonical Author: Aurélien Gâteau This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 2 or 3 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ #ifndef IMAGECONVERTER_H #define IMAGECONVERTER_H class QVariant; class QImage; namespace ImageConverter { /** * Returns a variant representing an image using the format describe in the - * galago spec + * freedesktop.org spec */ QVariant variantForImage(const QImage &image); } // namespace #endif /* IMAGECONVERTER_H */ diff --git a/src/notifybypopup.cpp b/src/notifybypopup.cpp index c472cce..043b9f5 100644 --- a/src/notifybypopup.cpp +++ b/src/notifybypopup.cpp @@ -1,471 +1,462 @@ /* Copyright (C) 2005-2009 by Olivier Goffart Copyright (C) 2008 by Dmitry Suzdalev Copyright (C) 2014 by Martin Klapetek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "notifybypopup.h" #include "imageconverter.h" #include "knotifyconfig.h" #include "knotification.h" #include "debug_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const char dbusServiceName[] = "org.freedesktop.Notifications"; static const char dbusInterfaceName[] = "org.freedesktop.Notifications"; static const char dbusPath[] = "/org/freedesktop/Notifications"; class NotifyByPopupPrivate { public: NotifyByPopupPrivate(NotifyByPopup *parent) : q(parent) {} /** * Sends notification to DBus "org.freedesktop.notifications" interface. * @param id knotify-sid identifier of notification * @param config notification data * @param update If true, will request the DBus service to update the notification with new data from \c notification * Otherwise will put new notification on screen * @return true for success or false if there was an error. */ - bool sendNotificationToGalagoServer(KNotification *notification, const KNotifyConfig &config, bool update = false); - /** - * Sends request to close Notification with id to DBus "org.freedesktop.notifications" interface - * @param id knotify-side notification ID to close - */ - void closeGalagoNotification(KNotification *notification); + bool sendNotificationToServer(KNotification *notification, const KNotifyConfig &config, bool update = false); + /** * Find the caption and the icon name of the application */ void getAppCaptionAndIconName(const KNotifyConfig &config, QString *appCaption, QString *iconName); /* * Query the dbus server for notification capabilities * If no DBus server is present, use fallback capabilities for KPassivePopup */ void queryPopupServerCapabilities(); /** * DBus notification daemon capabilities cache. * Do not use this variable. Use #popupServerCapabilities() instead. * @see popupServerCapabilities */ QStringList popupServerCapabilities; /** * In case we still don't know notification server capabilities, * we need to query those first. That's done in an async way * so we queue all notifications while waiting for the capabilities * to return, then process them from this queue */ QList > notificationQueue; /** * Whether the DBus notification daemon capability cache is up-to-date. */ bool dbusServiceCapCacheDirty; /* * As we communicate with the notification server over dbus * we use only ids, this is for fast KNotifications lookup */ - QHash> galagoNotifications; + QHash> notifications; NotifyByPopup * const q; }; //--------------------------------------------------------------------------------------- NotifyByPopup::NotifyByPopup(QObject *parent) : KNotificationPlugin(parent), d(new NotifyByPopupPrivate(this)) { d->dbusServiceCapCacheDirty = true; bool connected = QDBusConnection::sessionBus().connect(QString(), // from any service QString::fromLatin1(dbusPath), QString::fromLatin1(dbusInterfaceName), QStringLiteral("ActionInvoked"), this, - SLOT(onGalagoNotificationActionInvoked(uint,QString))); + SLOT(onNotificationActionInvoked(uint,QString))); if (!connected) { qCWarning(LOG_KNOTIFICATIONS) << "warning: failed to connect to ActionInvoked dbus signal"; } connected = QDBusConnection::sessionBus().connect(QString(), // from any service QString::fromLatin1(dbusPath), QString::fromLatin1(dbusInterfaceName), QStringLiteral("NotificationClosed"), this, - SLOT(onGalagoNotificationClosed(uint,uint))); + SLOT(onNotificationClosed(uint,uint))); if (!connected) { qCWarning(LOG_KNOTIFICATIONS) << "warning: failed to connect to NotificationClosed dbus signal"; } } NotifyByPopup::~NotifyByPopup() { delete d; } void NotifyByPopup::notify(KNotification *notification, KNotifyConfig *notifyConfig) { notify(notification, *notifyConfig); } void NotifyByPopup::notify(KNotification *notification, const KNotifyConfig ¬ifyConfig) { - if (d->galagoNotifications.contains(notification->id())) { + if (d->notifications.contains(notification->id())) { // notification is already on the screen, do nothing finish(notification); return; } if (d->dbusServiceCapCacheDirty) { // if we don't have the server capabilities yet, we need to query for them first; // as that is an async dbus operation, we enqueue the notification and process them // when we receive dbus reply with the server capabilities d->notificationQueue.append(qMakePair(notification, notifyConfig)); d->queryPopupServerCapabilities(); } else { - if (!d->sendNotificationToGalagoServer(notification, notifyConfig)) { + if (!d->sendNotificationToServer(notification, notifyConfig)) { finish(notification); //an error occurred. } } } void NotifyByPopup::update(KNotification *notification, KNotifyConfig *notifyConfig) { update(notification, *notifyConfig); } void NotifyByPopup::update(KNotification *notification, const KNotifyConfig ¬ifyConfig) { - d->sendNotificationToGalagoServer(notification, notifyConfig, true); + d->sendNotificationToServer(notification, notifyConfig, true); } void NotifyByPopup::close(KNotification *notification) { - d->closeGalagoNotification(notification); + uint id = d->notifications.key(notification, 0); + + if (id == 0) { + qCDebug(LOG_KNOTIFICATIONS) << "not found dbus id to close" << notification->id(); + return; + } + + QDBusMessage m = QDBusMessage::createMethodCall(QString::fromLatin1(dbusServiceName), QString::fromLatin1(dbusPath), + QString::fromLatin1(dbusInterfaceName), QStringLiteral("CloseNotification")); + QList args; + args.append(id); + m.setArguments(args); + + // send(..) does not block + bool queued = QDBusConnection::sessionBus().send(m); + + if (!queued) { + qCWarning(LOG_KNOTIFICATIONS) << "Failed to queue dbus message for closing a notification"; + } QMutableListIterator > iter(d->notificationQueue); while (iter.hasNext()) { auto &item = iter.next(); if (item.first == notification) { iter.remove(); } } } -void NotifyByPopup::onGalagoNotificationActionInvoked(uint notificationId, const QString &actionKey) +void NotifyByPopup::onNotificationActionInvoked(uint notificationId, const QString &actionKey) { - auto iter = d->galagoNotifications.find(notificationId); - if (iter == d->galagoNotifications.end()) { + auto iter = d->notifications.find(notificationId); + if (iter == d->notifications.end()) { return; } KNotification *n = *iter; if (n) { if (actionKey == QLatin1String("default")) { emit actionInvoked(n->id(), 0); } else { emit actionInvoked(n->id(), actionKey.toUInt()); } } else { - d->galagoNotifications.erase(iter); + d->notifications.erase(iter); } } -void NotifyByPopup::onGalagoNotificationClosed(uint dbus_id, uint reason) +void NotifyByPopup::onNotificationClosed(uint dbus_id, uint reason) { - auto iter = d->galagoNotifications.find(dbus_id); - if (iter == d->galagoNotifications.end()) { + auto iter = d->notifications.find(dbus_id); + if (iter == d->notifications.end()) { return; } KNotification *n = *iter; - d->galagoNotifications.remove(dbus_id); + d->notifications.remove(dbus_id); if (n) { emit finished(n); // The popup bubble is the only user facing part of a notification, // if the user closes the popup, it means he wants to get rid // of the notification completely, including playing sound etc // Therefore we close the KNotification completely after closing // the popup, but only if the reason is 2, which means "user closed" if (reason == 2) { n->close(); } } } -void NotifyByPopup::onGalagoServerReply(QDBusPendingCallWatcher *watcher) +void NotifyByPopup::onServerReply(QDBusPendingCallWatcher *watcher) { // call deleteLater first, since we might return in the middle of the function watcher->deleteLater(); KNotification *notification = watcher->property("notificationObject").value(); if (!notification) { qCWarning(LOG_KNOTIFICATIONS) << "Invalid notification object passed in DBus reply watcher; notification will probably break"; return; } QDBusPendingReply reply = *watcher; - d->galagoNotifications.insert(reply.argumentAt<0>(), notification); + d->notifications.insert(reply.argumentAt<0>(), notification); } -void NotifyByPopup::onGalagoServerCapabilitiesReceived(const QStringList &capabilities) +void NotifyByPopup::onServerCapabilitiesReceived(const QStringList &capabilities) { d->popupServerCapabilities = capabilities; d->dbusServiceCapCacheDirty = false; // re-run notify() on all enqueued notifications for (int i = 0, total = d->notificationQueue.size(); i < total; ++i) { notify(d->notificationQueue.at(i).first, d->notificationQueue.at(i).second); } d->notificationQueue.clear(); } void NotifyByPopupPrivate::getAppCaptionAndIconName(const KNotifyConfig ¬ifyConfig, QString *appCaption, QString *iconName) { KConfigGroup globalgroup(&(*notifyConfig.eventsfile), QStringLiteral("Global")); *appCaption = globalgroup.readEntry("Name", globalgroup.readEntry("Comment", notifyConfig.appname)); KConfigGroup eventGroup(&(*notifyConfig.eventsfile), QStringLiteral("Event/%1").arg(notifyConfig.eventid)); if (eventGroup.hasKey("IconName")) { *iconName = eventGroup.readEntry("IconName", notifyConfig.appname); } else { *iconName = globalgroup.readEntry("IconName", notifyConfig.appname); } } -bool NotifyByPopupPrivate::sendNotificationToGalagoServer(KNotification *notification, const KNotifyConfig ¬ifyConfig_nocheck, bool update) +bool NotifyByPopupPrivate::sendNotificationToServer(KNotification *notification, const KNotifyConfig ¬ifyConfig_nocheck, bool update) { - uint updateId = galagoNotifications.key(notification, 0); + uint updateId = notifications.key(notification, 0); if (update) { if (updateId == 0) { // we have nothing to update; the notification we're trying to update // has been already closed return false; } } QDBusMessage dbusNotificationMessage = QDBusMessage::createMethodCall(QString::fromLatin1(dbusServiceName), QString::fromLatin1(dbusPath), QString::fromLatin1(dbusInterfaceName), QStringLiteral("Notify")); QList args; QString appCaption; QString iconName; getAppCaptionAndIconName(notifyConfig_nocheck, &appCaption, &iconName); //did the user override the icon name? if (!notification->iconName().isEmpty()) { iconName = notification->iconName(); } args.append(appCaption); // app_name args.append(updateId); // notification to update args.append(iconName); // app_icon QString title = notification->title().isEmpty() ? appCaption : notification->title(); QString text = notification->text(); if (!popupServerCapabilities.contains(QLatin1String("body-markup"))) { title = q->stripRichText(title); text = q->stripRichText(text); } args.append(title); // summary args.append(text); // body - // galago spec defines action list to be list like + // freedesktop.org spec defines action list to be list like // (act_id1, action1, act_id2, action2, ...) // // assign id's to actions like it's done in fillPopup() method // (i.e. starting from 1) QStringList actionList; if (popupServerCapabilities.contains(QLatin1String("actions"))) { QString defaultAction = notification->defaultAction(); if (!defaultAction.isEmpty()) { actionList.append(QStringLiteral("default")); actionList.append(defaultAction); } int actId = 0; const auto listActions = notification->actions(); for (const QString &actionName : listActions) { actId++; actionList.append(QString::number(actId)); actionList.append(actionName); } } args.append(actionList); // actions QVariantMap hintsMap; // Add the application name to the hints. - // According to fdo spec, the app_name is supposed to be the application's "pretty name" + // According to freedesktop.org spec, the app_name is supposed to be the application's "pretty name" // but in some places it's handy to know the application name itself if (!notification->appName().isEmpty()) { hintsMap[QStringLiteral("x-kde-appname")] = notification->appName(); } if (!notification->eventId().isEmpty()) { hintsMap[QStringLiteral("x-kde-eventId")] = notification->eventId(); } if (notification->flags() & KNotification::SkipGrouping) { hintsMap[QStringLiteral("x-kde-skipGrouping")] = 1; } if (!notification->urls().isEmpty()) { hintsMap[QStringLiteral("x-kde-urls")] = QUrl::toStringList(notification->urls()); } if (!(notification->flags() & KNotification::Persistent)) { hintsMap[QStringLiteral("transient")] = true; } QString desktopFileName = QGuiApplication::desktopFileName(); if (!desktopFileName.isEmpty()) { // handle apps which set the desktopFileName property with filename suffix, // due to unclear API dox (https://bugreports.qt.io/browse/QTBUG-75521) if (desktopFileName.endsWith(QLatin1String(".desktop"))) { desktopFileName.chop(8); } hintsMap[QStringLiteral("desktop-entry")] = desktopFileName; } int urgency = -1; switch (notification->urgency()) { case KNotification::DefaultUrgency: break; case KNotification::LowUrgency: urgency = 0; break; case KNotification::NormalUrgency: Q_FALLTHROUGH(); - // galago notifications only know low, normal, critical + // freedesktop.org notifications only know low, normal, critical case KNotification::HighUrgency: urgency = 1; break; case KNotification::CriticalUrgency: urgency = 2; break; } if (urgency > -1) { hintsMap[QStringLiteral("urgency")] = urgency; } const QVariantMap hints = notification->hints(); for (auto it = hints.constBegin(); it != hints.constEnd(); ++it) { hintsMap[it.key()] = it.value(); } //FIXME - reenable/fix // let's see if we've got an image, and store the image in the hints map if (!notification->pixmap().isNull()) { QByteArray pixmapData; QBuffer buffer(&pixmapData); buffer.open(QIODevice::WriteOnly); notification->pixmap().save(&buffer, "PNG"); buffer.close(); hintsMap[QStringLiteral("image_data")] = ImageConverter::variantForImage(QImage::fromData(pixmapData)); } args.append(hintsMap); // hints // Persistent => 0 == infinite timeout // CloseOnTimeout => -1 == let the server decide int timeout = (notification->flags() & KNotification::Persistent) ? 0 : -1; args.append(timeout); // expire timeout dbusNotificationMessage.setArguments(args); QDBusPendingCall notificationCall = QDBusConnection::sessionBus().asyncCall(dbusNotificationMessage, -1); //parent is set to the notification so that no-one ever accesses a dangling pointer on the notificationObject property QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(notificationCall, notification); watcher->setProperty("notificationObject", QVariant::fromValue(notification)); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, - q, &NotifyByPopup::onGalagoServerReply); + q, &NotifyByPopup::onServerReply); return true; } -void NotifyByPopupPrivate::closeGalagoNotification(KNotification *notification) -{ - uint galagoId = galagoNotifications.key(notification, 0); - - if (galagoId == 0) { - qCDebug(LOG_KNOTIFICATIONS) << "not found dbus id to close" << notification->id(); - return; - } - - QDBusMessage m = QDBusMessage::createMethodCall(QString::fromLatin1(dbusServiceName), QString::fromLatin1(dbusPath), - QString::fromLatin1(dbusInterfaceName), QStringLiteral("CloseNotification")); - QList args; - args.append(galagoId); - m.setArguments(args); - - // send(..) does not block - bool queued = QDBusConnection::sessionBus().send(m); - - if (!queued) { - qCWarning(LOG_KNOTIFICATIONS) << "Failed to queue dbus message for closing a notification"; - } -} - void NotifyByPopupPrivate::queryPopupServerCapabilities() { if (dbusServiceCapCacheDirty) { QDBusMessage m = QDBusMessage::createMethodCall(QString::fromLatin1(dbusServiceName), QString::fromLatin1(dbusPath), QString::fromLatin1(dbusInterfaceName), QStringLiteral("GetCapabilities")); QDBusConnection::sessionBus().callWithCallback(m, q, - SLOT(onGalagoServerCapabilitiesReceived(QStringList)), + SLOT(onServerCapabilitiesReceived(QStringList)), nullptr, -1); } } diff --git a/src/notifybypopup.h b/src/notifybypopup.h index 9da5d3b..a93fd86 100644 --- a/src/notifybypopup.h +++ b/src/notifybypopup.h @@ -1,66 +1,66 @@ /* Copyright (C) 2005-2006 by Olivier Goffart Copyright (C) 2008 by Dmitry Suzdalev Copyright (C) 2014 by Martin Klapetek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef NOTIFYBYPOPUP_H #define NOTIFYBYPOPUP_H #include "knotificationplugin.h" #include class KNotification; class QDBusPendingCallWatcher; class NotifyByPopupPrivate; class NotifyByPopup : public KNotificationPlugin { Q_OBJECT public: explicit NotifyByPopup(QObject *parent = nullptr); ~NotifyByPopup() override; QString optionName() override { return QStringLiteral("Popup"); } void notify(KNotification *notification, KNotifyConfig *notifyConfig) override; void close(KNotification *notification) override; void update(KNotification *notification, KNotifyConfig *config) override; private Q_SLOTS: // slot which gets called when DBus signals that some notification action was invoked - void onGalagoNotificationActionInvoked(uint notificationId, const QString &actionKey); + void onNotificationActionInvoked(uint notificationId, const QString &actionKey); // slot which gets called when DBus signals that some notification was closed - void onGalagoNotificationClosed(uint, uint); + void onNotificationClosed(uint, uint); - void onGalagoServerReply(QDBusPendingCallWatcher *callWatcher); + void onServerReply(QDBusPendingCallWatcher *callWatcher); - void onGalagoServerCapabilitiesReceived(const QStringList &capabilities); + void onServerCapabilitiesReceived(const QStringList &capabilities); private: // TODO KF6, replace current public notify/update void notify(KNotification *notification, const KNotifyConfig ¬ifyConfig); void update(KNotification *notification, const KNotifyConfig ¬ifyConfig); NotifyByPopupPrivate * const d; friend class NotifyByPopupPrivate; }; #endif diff --git a/src/notifybyportal.cpp b/src/notifybyportal.cpp index d29a5ed..5151fa4 100644 --- a/src/notifybyportal.cpp +++ b/src/notifybyportal.cpp @@ -1,368 +1,368 @@ /* Copyright (C) 2005-2006 by Olivier Goffart Copyright (C) 2008 by Dmitry Suzdalev Copyright (C) 2014 by Martin Klapetek Copyright (C) 2016 Jan Grulich This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "notifybyportal.h" #include "knotifyconfig.h" #include "knotification.h" #include "debug_p.h" #include #include #include #include #include #include #include #include #include static const char portalDbusServiceName[] = "org.freedesktop.portal.Desktop"; static const char portalDbusInterfaceName[] = "org.freedesktop.portal.Notification"; static const char portalDbusPath[] = "/org/freedesktop/portal/desktop"; class NotifyByPortalPrivate { public: struct PortalIcon { QString str; QDBusVariant data; }; NotifyByPortalPrivate(NotifyByPortal *parent) : dbusServiceExists(false), q(parent) {} /** * Sends notification to DBus "org.freedesktop.notifications" interface. * @param id knotify-sid identifier of notification * @param config notification data * @param update If true, will request the DBus service to update the notification with new data from \c notification * Otherwise will put new notification on screen * @return true for success or false if there was an error. */ bool sendNotificationToPortal(KNotification *notification, const KNotifyConfig &config); /** * Sends request to close Notification with id to DBus "org.freedesktop.notifications" interface * @param id knotify-side notification ID to close */ void closePortalNotification(KNotification *notification); /** * Find the caption and the icon name of the application */ void getAppCaptionAndIconName(const KNotifyConfig &config, QString *appCaption, QString *iconName); /** * Specifies if DBus Notifications interface exists on session bus */ bool dbusServiceExists; /* * As we communicate with the notification server over dbus * we use only ids, this is for fast KNotifications lookup */ QHash> portalNotifications; /* * Holds the id that will be assigned to the next notification source * that will be created */ uint nextId; NotifyByPortal * const q; }; QDBusArgument &operator<<(QDBusArgument &argument, const NotifyByPortalPrivate::PortalIcon &icon) { argument.beginStructure(); argument << icon.str << icon.data; argument.endStructure(); return argument; } const QDBusArgument &operator>>(const QDBusArgument &argument, NotifyByPortalPrivate::PortalIcon &icon) { argument.beginStructure(); argument >> icon.str >> icon.data; argument.endStructure(); return argument; } Q_DECLARE_METATYPE(NotifyByPortalPrivate::PortalIcon) //--------------------------------------------------------------------------------------- NotifyByPortal::NotifyByPortal(QObject *parent) : KNotificationPlugin(parent), d(new NotifyByPortalPrivate(this)) { // check if service already exists on plugin instantiation QDBusConnectionInterface *interface = QDBusConnection::sessionBus().interface(); d->dbusServiceExists = interface && interface->isServiceRegistered(QString::fromLatin1(portalDbusServiceName)); if (d->dbusServiceExists) { onServiceOwnerChanged(QString::fromLatin1(portalDbusServiceName), QString(), QStringLiteral("_")); //connect signals } // to catch register/unregister events from service in runtime QDBusServiceWatcher *watcher = new QDBusServiceWatcher(this); watcher->setConnection(QDBusConnection::sessionBus()); watcher->setWatchMode(QDBusServiceWatcher::WatchForOwnerChange); watcher->addWatchedService(QString::fromLatin1(portalDbusServiceName)); connect(watcher,&QDBusServiceWatcher::serviceOwnerChanged, this, &NotifyByPortal::onServiceOwnerChanged); } NotifyByPortal::~NotifyByPortal() { delete d; } void NotifyByPortal::notify(KNotification *notification, KNotifyConfig *notifyConfig) { notify(notification, *notifyConfig); } void NotifyByPortal::notify(KNotification *notification, const KNotifyConfig ¬ifyConfig) { if (d->portalNotifications.contains(notification->id())) { // notification is already on the screen, do nothing finish(notification); return; } // check if Notifications DBus service exists on bus, use it if it does if (d->dbusServiceExists) { if (!d->sendNotificationToPortal(notification, notifyConfig)) { finish(notification); //an error occurred. } } } void NotifyByPortal::close(KNotification *notification) { if (d->dbusServiceExists) { d->closePortalNotification(notification); } } void NotifyByPortal::update(KNotification *notification, KNotifyConfig *notifyConfig) { // TODO not supported by portals Q_UNUSED(notification); Q_UNUSED(notifyConfig); } void NotifyByPortal::onServiceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner) { Q_UNUSED(serviceName); // close all notifications we currently hold reference to for (KNotification *n : qAsConst(d->portalNotifications)) { if (n) { emit finished(n); } } d->portalNotifications.clear(); if (newOwner.isEmpty()) { d->dbusServiceExists = false; } else if (oldOwner.isEmpty()) { d->dbusServiceExists = true; d->nextId = 1; // connect to action invocation signals bool connected = QDBusConnection::sessionBus().connect(QString(), // from any service QString::fromLatin1(portalDbusPath), QString::fromLatin1(portalDbusInterfaceName), QStringLiteral("ActionInvoked"), this, SLOT(onPortalNotificationActionInvoked(QString,QString,QVariantList))); if (!connected) { qCWarning(LOG_KNOTIFICATIONS) << "warning: failed to connect to ActionInvoked dbus signal"; } } } void NotifyByPortal::onPortalNotificationActionInvoked(const QString &id, const QString &action, const QVariantList ¶meter) { Q_UNUSED(parameter); auto iter = d->portalNotifications.find(id.toUInt()); if (iter == d->portalNotifications.end()) { return; } KNotification *n = *iter; if (n) { emit actionInvoked(n->id(), action.toUInt()); } else { d->portalNotifications.erase(iter); } } void NotifyByPortalPrivate::getAppCaptionAndIconName(const KNotifyConfig ¬ifyConfig, QString *appCaption, QString *iconName) { KConfigGroup globalgroup(&(*notifyConfig.eventsfile), QStringLiteral("Global")); *appCaption = globalgroup.readEntry("Name", globalgroup.readEntry("Comment", notifyConfig.appname)); KConfigGroup eventGroup(&(*notifyConfig.eventsfile), QStringLiteral("Event/%1").arg(notifyConfig.eventid)); if (eventGroup.hasKey("IconName")) { *iconName = eventGroup.readEntry("IconName", notifyConfig.appname); } else { *iconName = globalgroup.readEntry("IconName", notifyConfig.appname); } } bool NotifyByPortalPrivate::sendNotificationToPortal(KNotification *notification, const KNotifyConfig ¬ifyConfig_nocheck) { QDBusMessage dbusNotificationMessage; dbusNotificationMessage = QDBusMessage::createMethodCall(QString::fromLatin1(portalDbusServiceName), QString::fromLatin1(portalDbusPath), QString::fromLatin1(portalDbusInterfaceName), QStringLiteral("AddNotification")); QVariantList args; // Will be used only with xdg-desktop-portal QVariantMap portalArgs; QString appCaption; QString iconName; getAppCaptionAndIconName(notifyConfig_nocheck, &appCaption, &iconName); //did the user override the icon name? if (!notification->iconName().isEmpty()) { iconName = notification->iconName(); } QString title = notification->title().isEmpty() ? appCaption : notification->title(); QString text = notification->text(); if (!notification->defaultAction().isEmpty()) { portalArgs.insert(QStringLiteral("default-action"), notification->defaultAction()); portalArgs.insert(QStringLiteral("default-action-target"), QStringLiteral("0")); } QString priority; switch (notification->urgency()) { case KNotification::DefaultUrgency: break; case KNotification::LowUrgency: priority = QStringLiteral("low"); break; case KNotification::NormalUrgency: priority = QStringLiteral("normal"); break; case KNotification::HighUrgency: priority = QStringLiteral("high"); break; case KNotification::CriticalUrgency: priority = QStringLiteral("urgent"); break; } if (!priority.isEmpty()) { portalArgs.insert(QStringLiteral("priority"), priority); } - // galago spec defines action list to be list like + // freedesktop.org spec defines action list to be list like // (act_id1, action1, act_id2, action2, ...) // // assign id's to actions like it's done in fillPopup() method // (i.e. starting from 1) QList buttons; buttons.reserve(notification->actions().count()); int actId = 0; const auto listActions = notification->actions(); for (const QString &actionName : listActions) { actId++; QVariantMap button = { {QStringLiteral("action"), QString::number(actId)}, {QStringLiteral("label"), actionName} }; buttons << button; } qDBusRegisterMetaType >(); qDBusRegisterMetaType(); if (!notification->pixmap().isNull()) { QByteArray pixmapData; QBuffer buffer(&pixmapData); buffer.open(QIODevice::WriteOnly); notification->pixmap().save(&buffer, "PNG"); buffer.close(); PortalIcon icon; icon.str = QStringLiteral("bytes"); icon.data.setVariant(pixmapData); portalArgs.insert(QStringLiteral("icon"), QVariant::fromValue(icon)); } else { // Use this for now for backwards compatibility, we can as well set the variant to be (sv) where the // string is keyword "themed" and the variant is an array of strings with icon names portalArgs.insert(QStringLiteral("icon"), iconName); } portalArgs.insert(QStringLiteral("title"), title); portalArgs.insert(QStringLiteral("body"), text); portalArgs.insert(QStringLiteral("buttons"), QVariant::fromValue >(buttons)); args.append(QString::number(nextId)); args.append(portalArgs); dbusNotificationMessage.setArguments(args); QDBusPendingCall notificationCall = QDBusConnection::sessionBus().asyncCall(dbusNotificationMessage, -1); // If we are in sandbox we don't need to wait for returned notification id portalNotifications.insert(nextId++, notification); return true; } void NotifyByPortalPrivate::closePortalNotification(KNotification *notification) { uint id = portalNotifications.key(notification, 0); qCDebug(LOG_KNOTIFICATIONS) << "ID: " << id; if (id == 0) { qCDebug(LOG_KNOTIFICATIONS) << "not found dbus id to close" << notification->id(); return; } QDBusMessage m = QDBusMessage::createMethodCall(QString::fromLatin1(portalDbusServiceName), QString::fromLatin1(portalDbusPath), QString::fromLatin1(portalDbusInterfaceName), QStringLiteral("RemoveNotification")); m.setArguments({QString::number(id)}); // send(..) does not block bool queued = QDBusConnection::sessionBus().send(m); if (!queued) { qCWarning(LOG_KNOTIFICATIONS) << "Failed to queue dbus message for closing a notification"; } }