File indexing completed on 2024-04-28 05:36:51

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 static QString appPathFromId(const QString &app_id)
0028 {
0029     QString ret = QLatin1Char('/') + app_id;
0030     ret.replace('.', '/');
0031     ret.replace('-', '_');
0032     return ret;
0033 }
0034 
0035 void NotificationPortal::AddNotification(const QString &app_id, const QString &id, const QVariantMap &notification)
0036 {
0037     qCDebug(XdgDesktopPortalKdeNotification) << "AddNotification called with parameters:";
0038     qCDebug(XdgDesktopPortalKdeNotification) << "    app_id: " << app_id;
0039     qCDebug(XdgDesktopPortalKdeNotification) << "    id: " << id;
0040     qCDebug(XdgDesktopPortalKdeNotification) << "    notification: " << notification;
0041 
0042     // We have to use "notification" as an ID because any other ID will not be configured
0043     KNotification *notify = new KNotification(QStringLiteral("notification"), KNotification::CloseOnTimeout | KNotification::DefaultEvent, this);
0044     if (notification.contains(QStringLiteral("title"))) {
0045         notify->setTitle(notification.value(QStringLiteral("title")).toString());
0046     }
0047     if (notification.contains(QStringLiteral("body"))) {
0048         notify->setText(notification.value(QStringLiteral("body")).toString());
0049     }
0050     if (notification.contains(QStringLiteral("icon"))) {
0051         QVariant iconVariant = notification.value(QStringLiteral("icon"));
0052         if (iconVariant.type() == QVariant::String) {
0053             notify->setIconName(iconVariant.toString());
0054         } else {
0055             QDBusArgument argument = iconVariant.value<QDBusArgument>();
0056             PortalIcon icon = qdbus_cast<PortalIcon>(argument);
0057             QVariant iconData = icon.data.variant();
0058             if (icon.str == QStringLiteral("themed") && iconData.type() == QVariant::StringList) {
0059                 notify->setIconName(iconData.toStringList().first());
0060             } else if (icon.str == QStringLiteral("bytes") && iconData.type() == QVariant::ByteArray) {
0061                 QPixmap pixmap;
0062                 if (pixmap.loadFromData(iconData.toByteArray(), "PNG")) {
0063                     notify->setPixmap(pixmap);
0064                 }
0065             }
0066         }
0067     }
0068 
0069     const QString priority = notification.value(QStringLiteral("priority")).toString();
0070     if (priority == QLatin1String("low")) {
0071         notify->setUrgency(KNotification::LowUrgency);
0072     } else if (priority == QLatin1String("normal")) {
0073         notify->setUrgency(KNotification::NormalUrgency);
0074     } else if (priority == QLatin1String("high")) {
0075         notify->setUrgency(KNotification::HighUrgency);
0076     } else if (priority == QLatin1String("urgent")) {
0077         notify->setUrgency(KNotification::CriticalUrgency);
0078     }
0079 
0080     auto actionInvoked = [notify, app_id, id](const QString &actionId, const QVariant &actionTarget) {
0081         QVariantMap platformData;
0082         if (!notify->xdgActivationToken().isEmpty()) {
0083             platformData = {
0084                 {QStringLiteral("activation-token"), notify->xdgActivationToken()},
0085 
0086                 // apparently gtk uses "desktop-startup-id"
0087                 {QStringLiteral("desktop-startup-id"), notify->xdgActivationToken()},
0088             };
0089         }
0090 
0091         qCDebug(XdgDesktopPortalKdeNotification) << "Notification activated:";
0092         qCDebug(XdgDesktopPortalKdeNotification) << "    app_id: " << app_id;
0093         qCDebug(XdgDesktopPortalKdeNotification) << "    id: " << id;
0094         qCDebug(XdgDesktopPortalKdeNotification) << "    action: " << actionId << actionTarget;
0095 
0096         QVariantList params;
0097         if (actionTarget.isValid()) {
0098             params += actionTarget;
0099         }
0100 
0101         OrgFreedesktopApplicationInterface iface(app_id, appPathFromId(app_id), QDBusConnection::sessionBus());
0102         if (actionId.startsWith("app.") && iface.isValid()) {
0103             iface.ActivateAction(actionId.mid(4), params, platformData);
0104         } else {
0105             if (iface.isValid()) {
0106                 iface.Activate(platformData);
0107             }
0108 
0109             QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/org/freedesktop/portal/desktop"),
0110                                                               QStringLiteral("org.freedesktop.impl.portal.Notification"),
0111                                                               QStringLiteral("ActionInvoked"));
0112             message << app_id << id << actionId << params;
0113             QDBusConnection::sessionBus().send(message);
0114         }
0115     };
0116 
0117     if (notification.contains(QStringLiteral("default-action")) && notification.contains(QStringLiteral("default-action-target"))) {
0118         KNotificationAction *action = notify->addDefaultAction(notification.value(QStringLiteral("default-action")).toString());
0119 
0120         connect(action, &KNotificationAction::activated, this, [actionInvoked, notification] {
0121             actionInvoked(notification.value(QStringLiteral("default-action")).toString(), notification.value(QStringLiteral("default-action-target")));
0122         });
0123     }
0124 
0125     if (notification.contains(QStringLiteral("buttons"))) {
0126         const QDBusArgument dbusArgument = notification.value(QStringLiteral("buttons")).value<QDBusArgument>();
0127         const auto buttons = qdbus_cast<QList<QVariantMap>>(dbusArgument);
0128 
0129         for (const QVariantMap &button : std::as_const(buttons)) {
0130             auto action = notify->addAction(button.value(QStringLiteral("label")).toString());
0131             connect(action, &KNotificationAction::activated, this, [button, actionInvoked] {
0132                 actionInvoked(button.value(QStringLiteral("action")).toString(), button.value(QStringLiteral("target")));
0133             });
0134         }
0135     }
0136     notify->setHint(QStringLiteral("desktop-entry"), app_id);
0137     notify->setHint(QStringLiteral("x-kde-xdgTokenAppId"), app_id);
0138 
0139     connect(notify, &KNotification::closed, this, &NotificationPortal::notificationClosed);
0140 
0141     m_notifications.insert(QStringLiteral("%1:%2").arg(app_id, id), notify);
0142     notify->sendEvent();
0143 }
0144 
0145 void NotificationPortal::RemoveNotification(const QString &app_id, const QString &id)
0146 {
0147     qCDebug(XdgDesktopPortalKdeNotification) << "RemoveNotification called with parameters:";
0148     qCDebug(XdgDesktopPortalKdeNotification) << "    app_id: " << app_id;
0149     qCDebug(XdgDesktopPortalKdeNotification) << "    id: " << id;
0150 
0151     KNotification *notify = m_notifications.take(QStringLiteral("%1:%2").arg(app_id, id));
0152     if (notify) {
0153         disconnect(notify, &KNotification::closed, this, &NotificationPortal::notificationClosed);
0154         notify->close();
0155     }
0156 }
0157 
0158 void NotificationPortal::notificationClosed()
0159 {
0160     KNotification *notify = qobject_cast<KNotification *>(sender());
0161     Q_ASSERT(notify);
0162     const QString appId = notify->property("app_id").toString();
0163     const QString id = notify->property("id").toString();
0164 
0165     auto n = m_notifications.take(QStringLiteral("%1:%2").arg(appId, id));
0166     if (n) {
0167         Q_ASSERT(n == notify);
0168         n->close();
0169     }
0170 }