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