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 ¬ification) { 0044 notificationAdded(notification); 0045 }); 0046 0047 connect(&Server::self(), &Server::notificationReplaced, this, [this](uint replacedId, const Notification ¬ification) { 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 ¬ification) 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"