File indexing completed on 2024-12-08 13:27:06

0001 /*
0002  * SPDX-FileCopyrightText: 2016 Red Hat Inc
0003  * SPDX-License-Identifier: LGPL-2.0-or-later
0004  *
0005  * SPDX-FileCopyrightText: 2016 Jan Grulich <jgrulich@redhat.com>
0006  */
0007 
0008 #include "notification.h"
0009 
0010 #include <QDBusConnection>
0011 #include <QDBusMessage>
0012 #include <QDBusMetaType>
0013 
0014 #include "fdo_application_interface.h"
0015 #include "notification_debug.h"
0016 #include "portalicon.h"
0017 
0018 using Action = QPair<QString, QVariant>;
0019 Q_DECLARE_METATYPE(QList<Action>);
0020 
0021 NotificationPortal::NotificationPortal(QObject *parent)
0022     : QDBusAbstractAdaptor(parent)
0023 {
0024     PortalIcon::registerDBusType();
0025 }
0026 
0027 NotificationPortal::~NotificationPortal()
0028 {
0029 }
0030 
0031 void NotificationPortal::AddNotification(const QString &app_id, const QString &id, const QVariantMap &notification)
0032 {
0033     qCDebug(XdgDesktopPortalKdeNotification) << "AddNotification called with parameters:";
0034     qCDebug(XdgDesktopPortalKdeNotification) << "    app_id: " << app_id;
0035     qCDebug(XdgDesktopPortalKdeNotification) << "    id: " << id;
0036     qCDebug(XdgDesktopPortalKdeNotification) << "    notification: " << notification;
0037 
0038     // We have to use "notification" as an ID because any other ID will not be configured
0039     KNotification *notify = new KNotification(QStringLiteral("notification"), KNotification::CloseOnTimeout | KNotification::DefaultEvent, this);
0040     if (notification.contains(QStringLiteral("title"))) {
0041         notify->setTitle(notification.value(QStringLiteral("title")).toString());
0042     }
0043     if (notification.contains(QStringLiteral("body"))) {
0044         notify->setText(notification.value(QStringLiteral("body")).toString());
0045     }
0046     if (notification.contains(QStringLiteral("icon"))) {
0047         QVariant iconVariant = notification.value(QStringLiteral("icon"));
0048         if (iconVariant.type() == QVariant::String) {
0049             notify->setIconName(iconVariant.toString());
0050         } else {
0051             QDBusArgument argument = iconVariant.value<QDBusArgument>();
0052             PortalIcon icon = qdbus_cast<PortalIcon>(argument);
0053             QVariant iconData = icon.data.variant();
0054             if (icon.str == QStringLiteral("themed") && iconData.type() == QVariant::StringList) {
0055                 notify->setIconName(iconData.toStringList().first());
0056             } else if (icon.str == QStringLiteral("bytes") && iconData.type() == QVariant::ByteArray) {
0057                 QPixmap pixmap;
0058                 if (pixmap.loadFromData(iconData.toByteArray(), "PNG")) {
0059                     notify->setPixmap(pixmap);
0060                 }
0061             }
0062         }
0063     }
0064 
0065     const QString priority = notification.value(QStringLiteral("priority")).toString();
0066     if (priority == QLatin1String("low")) {
0067         notify->setUrgency(KNotification::LowUrgency);
0068     } else if (priority == QLatin1String("normal")) {
0069         notify->setUrgency(KNotification::NormalUrgency);
0070     } else if (priority == QLatin1String("high")) {
0071         notify->setUrgency(KNotification::HighUrgency);
0072     } else if (priority == QLatin1String("urgent")) {
0073         notify->setUrgency(KNotification::CriticalUrgency);
0074     }
0075 
0076     if (notification.contains(QStringLiteral("default-action")) && notification.contains(QStringLiteral("default-action-target"))) {
0077         // default action is conveniently mapped to action number 0 so it uses the same action invocation method as the others
0078         notify->setDefaultAction(notification.value(QStringLiteral("default-action")).toString());
0079     }
0080 
0081     QList<Action> actionValues = {
0082         {notification.value(QStringLiteral("default-action")).toString(), notification.value(QStringLiteral("default-action-target"))}};
0083     if (notification.contains(QStringLiteral("buttons"))) {
0084         const QDBusArgument dbusArgument = notification.value(QStringLiteral("buttons")).value<QDBusArgument>();
0085         const auto buttons = qdbus_cast<QList<QVariantMap>>(dbusArgument);
0086 
0087         QStringList actions;
0088         actions.reserve(buttons.count());
0089         for (const QVariantMap &button : qAsConst(buttons)) {
0090             actions << button.value(QStringLiteral("label")).toString();
0091 
0092             actionValues.append({button.value(QStringLiteral("action")).toString(), button.value(QStringLiteral("target"))});
0093         }
0094 
0095         if (!actions.isEmpty()) {
0096             notify->setActions(actions);
0097         }
0098     }
0099     notify->setHint(QStringLiteral("desktop-entry"), app_id);
0100     notify->setHint(QStringLiteral("x-kde-xdgTokenAppId"), app_id);
0101 
0102     notify->setProperty("actionValues", QVariant::fromValue<QList<Action>>(actionValues));
0103     notify->setProperty("app_id", app_id);
0104     notify->setProperty("id", id);
0105     connect(notify, static_cast<void (KNotification::*)(uint)>(&KNotification::activated), this, &NotificationPortal::notificationActivated);
0106     connect(notify, &KNotification::closed, this, &NotificationPortal::notificationClosed);
0107 
0108     m_notifications.insert(QStringLiteral("%1:%2").arg(app_id, id), notify);
0109     notify->sendEvent();
0110 }
0111 
0112 static QString appPathFromId(const QString &app_id)
0113 {
0114     QString ret = QLatin1Char('/') + app_id;
0115     ret.replace('.', '/');
0116     ret.replace('-', '_');
0117     return ret;
0118 }
0119 
0120 void NotificationPortal::notificationActivated(uint action)
0121 {
0122     KNotification *notify = qobject_cast<KNotification *>(sender());
0123 
0124     if (!notify) {
0125         return;
0126     }
0127 
0128     const QString appId = notify->property("app_id").toString();
0129     const QString id = notify->property("id").toString();
0130     const auto actionValues = notify->property("actionValues").value<QList<Action>>();
0131     QVariantMap platformData;
0132     if (!notify->xdgActivationToken().isEmpty()) {
0133         platformData = {
0134             {QStringLiteral("activation-token"), notify->xdgActivationToken()},
0135 
0136             // apparently gtk uses "desktop-startup-id"
0137             {QStringLiteral("desktop-startup-id"), notify->xdgActivationToken()},
0138         };
0139     }
0140 
0141     qCDebug(XdgDesktopPortalKdeNotification) << "Notification activated:";
0142     qCDebug(XdgDesktopPortalKdeNotification) << "    app_id: " << appId;
0143     qCDebug(XdgDesktopPortalKdeNotification) << "    id: " << id;
0144     qCDebug(XdgDesktopPortalKdeNotification) << "    action: " << action << actionValues;
0145 
0146     const auto ourAction = actionValues.value(action);
0147 
0148     QVariantList params;
0149     if (ourAction.second.isValid()) {
0150         params += ourAction.second;
0151     }
0152 
0153     OrgFreedesktopApplicationInterface iface(appId, appPathFromId(appId), QDBusConnection::sessionBus());
0154     if (ourAction.first.startsWith("app.") && iface.isValid()) {
0155         iface.ActivateAction(ourAction.first.mid(4), params, platformData);
0156     } else {
0157         if (iface.isValid()) {
0158             iface.Activate(platformData);
0159         }
0160 
0161         QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/org/freedesktop/portal/desktop"),
0162                                                           QStringLiteral("org.freedesktop.impl.portal.Notification"),
0163                                                           QStringLiteral("ActionInvoked"));
0164         message << appId << id << ourAction.first << params;
0165         QDBusConnection::sessionBus().send(message);
0166     }
0167 }
0168 
0169 void NotificationPortal::RemoveNotification(const QString &app_id, const QString &id)
0170 {
0171     qCDebug(XdgDesktopPortalKdeNotification) << "RemoveNotification called with parameters:";
0172     qCDebug(XdgDesktopPortalKdeNotification) << "    app_id: " << app_id;
0173     qCDebug(XdgDesktopPortalKdeNotification) << "    id: " << id;
0174 
0175     KNotification *notify = m_notifications.take(QStringLiteral("%1:%2").arg(app_id, id));
0176     if (notify) {
0177         disconnect(notify, &KNotification::closed, this, &NotificationPortal::notificationClosed);
0178         notify->close();
0179     }
0180 }
0181 
0182 void NotificationPortal::notificationClosed()
0183 {
0184     KNotification *notify = qobject_cast<KNotification *>(sender());
0185     Q_ASSERT(notify);
0186     const QString appId = notify->property("app_id").toString();
0187     const QString id = notify->property("id").toString();
0188 
0189     auto n = m_notifications.take(QStringLiteral("%1:%2").arg(appId, id));
0190     if (n) {
0191         Q_ASSERT(n == notify);
0192         n->close();
0193     }
0194 }