File indexing completed on 2024-05-19 05:37:51

0001 /*
0002     SPDX-FileCopyrightText: 2008 Dmitry Suzdalev <dimsuz@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "notificationsengine.h"
0008 #include "notificationservice.h"
0009 
0010 #include "notification.h"
0011 #include "server.h"
0012 
0013 #include <KConfig>
0014 #include <KConfigGroup>
0015 #include <KNotifyConfigWidget>
0016 #include <KService>
0017 #include <KSharedConfig>
0018 #include <QGuiApplication>
0019 #include <klocalizedstring.h>
0020 
0021 #include <Plasma5Support/DataContainer>
0022 #include <Plasma5Support/Service>
0023 
0024 #include <QImage>
0025 #include <QPointer>
0026 
0027 #include "debug.h"
0028 
0029 using namespace NotificationManager;
0030 
0031 NotificationsEngine::NotificationsEngine(QObject *parent)
0032     : Plasma5Support::DataEngine(parent)
0033 {
0034     init();
0035 }
0036 
0037 NotificationsEngine::~NotificationsEngine()
0038 {
0039 }
0040 
0041 void NotificationsEngine::init()
0042 {
0043     connect(&Server::self(), &Server::notificationAdded, this, [this](const Notification &notification) {
0044         notificationAdded(notification);
0045     });
0046 
0047     connect(&Server::self(), &Server::notificationReplaced, this, [this](uint replacedId, const Notification &notification) {
0048         // Notification will already have the correct identical ID
0049         Q_UNUSED(replacedId);
0050         notificationAdded(notification);
0051     });
0052 
0053     connect(&Server::self(), &Server::notificationRemoved, this, [this](uint id, Server::CloseReason reason) {
0054         Q_UNUSED(reason);
0055         const QString source = QStringLiteral("notification %1").arg(id);
0056         // if we don't have that notification in our local list,
0057         // it has already been closed so don't notify a second time
0058         if (m_activeNotifications.remove(source) > 0) {
0059             removeSource(source);
0060         }
0061     });
0062 
0063     Server::self().init();
0064 }
0065 
0066 void NotificationsEngine::notificationAdded(const Notification &notification)
0067 {
0068     const QString app_name = notification.applicationName();
0069     const QString appRealName = notification.notifyRcName();
0070     const QString eventId = notification.eventId(); // FIXME = hints[QStringLiteral("x-kde-eventId")].toString();
0071     const QStringList urls = QUrl::toStringList(notification.urls());
0072     const QString desktopEntry = notification.desktopEntry();
0073     const QString summary = notification.summary();
0074 
0075     QString bodyFinal = notification.body(); // is already sanitized by NotificationManager
0076     QString summaryFinal = notification.summary();
0077     int timeout = notification.timeout();
0078 
0079     if (bodyFinal.isEmpty()) {
0080         // some ridiculous apps will send just a title (#372112), in that case, treat it as though there's only a body
0081         bodyFinal = summary;
0082         summaryFinal = app_name;
0083     }
0084 
0085     uint id = notification.id(); // replaces_id ? replaces_id : m_nextId++;
0086 
0087     QString appname_str = app_name;
0088     if (appname_str.isEmpty()) {
0089         appname_str = i18n("Unknown Application");
0090     }
0091 
0092     bool isPersistent = (timeout == 0);
0093 
0094     const int AVERAGE_WORD_LENGTH = 6;
0095     const int WORD_PER_MINUTE = 250;
0096     int count = notification.summary().length() + notification.body().length() - strlen("<?xml version=\"1.0\"><html></html>");
0097 
0098     // -1 is "server default", 0 is persistent with "server default" display time,
0099     // anything more should honor the setting
0100     if (timeout <= 0) {
0101         timeout = 60000 * count / AVERAGE_WORD_LENGTH / WORD_PER_MINUTE;
0102 
0103         // Add two seconds for the user to notice the notification, and ensure
0104         // it last at least five seconds, otherwise all the user see is a
0105         // flash
0106         timeout = 2000 + qMax(timeout, 3000);
0107     }
0108 
0109     const QString source = QStringLiteral("notification %1").arg(id);
0110 
0111     Plasma5Support::DataEngine::Data notificationData;
0112     notificationData.insert(QStringLiteral("id"), QString::number(id));
0113     notificationData.insert(QStringLiteral("eventId"), eventId);
0114     notificationData.insert(QStringLiteral("appName"), notification.applicationName());
0115     // TODO should be proper passed in icon?
0116     notificationData.insert(QStringLiteral("appIcon"), notification.applicationIconName());
0117     notificationData.insert(QStringLiteral("summary"), summaryFinal);
0118     notificationData.insert(QStringLiteral("body"), bodyFinal);
0119 
0120     QStringList actions;
0121     for (int i = 0; i < notification.actionNames().count(); ++i) {
0122         actions << notification.actionNames().at(i) << notification.actionLabels().at(i);
0123     }
0124     // NotificationManager hides the configure and default stuff from us but we need to re-add them
0125     // to the actions list for compatibility
0126     if (!notification.configureActionLabel().isEmpty()) {
0127         actions << QStringLiteral("settings") << notification.configureActionLabel();
0128     }
0129     if (notification.hasDefaultAction()) {
0130         actions << QStringLiteral("default") << QString();
0131     }
0132 
0133     notificationData.insert(QStringLiteral("actions"), actions);
0134     notificationData.insert(QStringLiteral("isPersistent"), isPersistent);
0135     notificationData.insert(QStringLiteral("expireTimeout"), timeout);
0136 
0137     notificationData.insert(QStringLiteral("desktopEntry"), desktopEntry);
0138 
0139     KService::Ptr service = KService::serviceByStorageId(desktopEntry);
0140     if (service) {
0141         notificationData.insert(QStringLiteral("appServiceName"), service->name());
0142         notificationData.insert(QStringLiteral("appServiceIcon"), service->icon());
0143     }
0144 
0145     notificationData.insert(QStringLiteral("appRealName"), appRealName);
0146     // NotificationManager configurable is anything that has a notifyrc or desktop entry
0147     // but the old stuff assumes only stuff with notifyrc to be configurable
0148     notificationData.insert(QStringLiteral("configurable"), !notification.notifyRcName().isEmpty());
0149 
0150     QImage image = notification.image();
0151     notificationData.insert(QStringLiteral("image"), image.isNull() ? QVariant() : image);
0152 
0153     int urgency = -1;
0154     switch (notification.urgency()) {
0155     case Notifications::LowUrgency:
0156         urgency = 0;
0157         break;
0158     case Notifications::NormalUrgency:
0159         urgency = 1;
0160         break;
0161     case Notifications::CriticalUrgency:
0162         urgency = 2;
0163         break;
0164     }
0165 
0166     if (urgency > -1) {
0167         notificationData.insert(QStringLiteral("urgency"), urgency);
0168     }
0169 
0170     notificationData.insert(QStringLiteral("urls"), urls);
0171 
0172     setData(source, notificationData);
0173 
0174     m_activeNotifications.insert(source, notification.applicationName() + notification.summary());
0175 }
0176 
0177 void NotificationsEngine::removeNotification(uint id, uint closeReason)
0178 {
0179     const QString source = QStringLiteral("notification %1").arg(id);
0180     // if we don't have that notification in our local list,
0181     // it has already been closed so don't notify a second time
0182     if (m_activeNotifications.remove(source) > 0) {
0183         removeSource(source);
0184         Server::self().closeNotification(id, static_cast<Server::CloseReason>(closeReason));
0185     }
0186 }
0187 
0188 Plasma5Support::Service *NotificationsEngine::serviceForSource(const QString &source)
0189 {
0190     return new NotificationService(this, source);
0191 }
0192 
0193 int NotificationsEngine::createNotification(const QString &appName,
0194                                             const QString &appIcon,
0195                                             const QString &summary,
0196                                             const QString &body,
0197                                             int timeout,
0198                                             const QStringList &actions,
0199                                             const QVariantMap &hints)
0200 {
0201     Notification notification;
0202     notification.setApplicationName(appName);
0203     notification.setApplicationIconName(appIcon);
0204     notification.setSummary(summary);
0205     notification.setBody(body); // sanitizes
0206     notification.setActions(actions);
0207     notification.setTimeout(timeout);
0208     notification.processHints(hints);
0209     Server::self().add(notification);
0210     return 0;
0211 }
0212 
0213 void NotificationsEngine::configureNotification(const QString &appName, const QString &eventId)
0214 {
0215     KNotifyConfigWidget *widget = KNotifyConfigWidget::configure(nullptr, appName);
0216     if (!eventId.isEmpty()) {
0217         widget->selectEvent(eventId);
0218     }
0219 }
0220 
0221 NotificationInhibitonPtr NotificationsEngine::createInhibition(const QString &hint, const QString &value)
0222 {
0223     auto ni = new NotificationInhibiton;
0224     ni->hint = hint;
0225     ni->value = value;
0226 
0227     QPointer<NotificationsEngine> guard(this);
0228     NotificationInhibitonPtr rc(ni, [this, guard](NotificationInhibiton *ni) {
0229         if (guard) {
0230             m_inhibitions.removeOne(ni);
0231         }
0232         delete ni;
0233     });
0234     m_inhibitions.append(ni);
0235     return rc;
0236 }
0237 
0238 K_PLUGIN_CLASS_WITH_JSON(NotificationsEngine, "plasma-dataengine-notifications.json")
0239 
0240 #include "notificationsengine.moc"