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 ¬ification) 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 }