Changeset View
Changeset View
Standalone View
Standalone View
src/notifybypopup.cpp
Show All 16 Lines | 1 | /* | |||
---|---|---|---|---|---|
17 | Lesser General Public License for more details. | 17 | Lesser General Public License for more details. | ||
18 | 18 | | |||
19 | You should have received a copy of the GNU Lesser General Public | 19 | You should have received a copy of the GNU Lesser General Public | ||
20 | License along with this library. If not, see <http://www.gnu.org/licenses/>. | 20 | License along with this library. If not, see <http://www.gnu.org/licenses/>. | ||
21 | 21 | | |||
22 | */ | 22 | */ | ||
23 | 23 | | |||
24 | #include "notifybypopup.h" | 24 | #include "notifybypopup.h" | ||
25 | #include "imageconverter.h" | | |||
26 | 25 | | |||
26 | #include "imageconverter.h" | ||||
27 | #include "knotifyconfig.h" | 27 | #include "knotifyconfig.h" | ||
28 | #include "knotification.h" | 28 | #include "knotification.h" | ||
29 | #include "notifications_interface.h" | ||||
29 | #include "debug_p.h" | 30 | #include "debug_p.h" | ||
30 | 31 | | |||
31 | #include <QBuffer> | 32 | #include <QBuffer> | ||
32 | #include <QGuiApplication> | 33 | #include <QGuiApplication> | ||
33 | #include <QDBusConnection> | 34 | #include <QDBusConnection> | ||
34 | #include <QDBusConnectionInterface> | | |||
35 | #include <QDBusServiceWatcher> | | |||
36 | #include <QDBusMessage> | | |||
37 | #include <QXmlStreamReader> | | |||
38 | #include <QMap> | | |||
39 | #include <QHash> | 35 | #include <QHash> | ||
40 | #include <QPointer> | 36 | #include <QPointer> | ||
41 | #include <QMutableListIterator> | 37 | #include <QMutableListIterator> | ||
42 | #include <QThread> | | |||
43 | #include <QFontMetrics> | | |||
44 | #include <QIcon> | | |||
45 | #include <QUrl> | 38 | #include <QUrl> | ||
46 | 39 | | |||
47 | #include <kconfiggroup.h> | 40 | #include <kconfiggroup.h> | ||
48 | 41 | | |||
49 | static const char dbusServiceName[] = "org.freedesktop.Notifications"; | | |||
50 | static const char dbusInterfaceName[] = "org.freedesktop.Notifications"; | | |||
51 | static const char dbusPath[] = "/org/freedesktop/Notifications"; | | |||
52 | | ||||
53 | class NotifyByPopupPrivate { | 42 | class NotifyByPopupPrivate { | ||
54 | public: | 43 | public: | ||
55 | NotifyByPopupPrivate(NotifyByPopup *parent) : q(parent) {} | 44 | NotifyByPopupPrivate(NotifyByPopup *parent) | ||
45 | : dbusInterface(QStringLiteral("org.freedesktop.Notifications"), | ||||
46 | QStringLiteral("/org/freedesktop/Notifications"), | ||||
47 | QDBusConnection::sessionBus()) | ||||
48 | , q(parent) {} | ||||
56 | 49 | | |||
57 | /** | 50 | /** | ||
58 | * Sends notification to DBus "org.freedesktop.notifications" interface. | 51 | * Sends notification to DBus "org.freedesktop.notifications" interface. | ||
59 | * @param id knotify-sid identifier of notification | 52 | * @param id knotify-sid identifier of notification | ||
60 | * @param config notification data | 53 | * @param config notification data | ||
61 | * @param update If true, will request the DBus service to update | 54 | * @param update If true, will request the DBus service to update | ||
62 | the notification with new data from \c notification | 55 | the notification with new data from \c notification | ||
63 | * Otherwise will put new notification on screen | 56 | * Otherwise will put new notification on screen | ||
Show All 31 Lines | |||||
95 | bool dbusServiceCapCacheDirty; | 88 | bool dbusServiceCapCacheDirty; | ||
96 | 89 | | |||
97 | /* | 90 | /* | ||
98 | * As we communicate with the notification server over dbus | 91 | * As we communicate with the notification server over dbus | ||
99 | * we use only ids, this is for fast KNotifications lookup | 92 | * we use only ids, this is for fast KNotifications lookup | ||
100 | */ | 93 | */ | ||
101 | QHash<uint, QPointer<KNotification>> notifications; | 94 | QHash<uint, QPointer<KNotification>> notifications; | ||
102 | 95 | | |||
96 | org::freedesktop::Notifications dbusInterface; | ||||
103 | 97 | | |||
104 | NotifyByPopup * const q; | 98 | NotifyByPopup * const q; | ||
105 | }; | 99 | }; | ||
106 | 100 | | |||
107 | //--------------------------------------------------------------------------------------- | 101 | //--------------------------------------------------------------------------------------- | ||
108 | 102 | | |||
109 | NotifyByPopup::NotifyByPopup(QObject *parent) | 103 | NotifyByPopup::NotifyByPopup(QObject *parent) | ||
110 | : KNotificationPlugin(parent), | 104 | : KNotificationPlugin(parent), | ||
111 | d(new NotifyByPopupPrivate(this)) | 105 | d(new NotifyByPopupPrivate(this)) | ||
112 | { | 106 | { | ||
113 | d->dbusServiceCapCacheDirty = true; | 107 | d->dbusServiceCapCacheDirty = true; | ||
114 | 108 | | |||
115 | bool connected = QDBusConnection::sessionBus().connect(QString(), // from any service | 109 | connect(&d->dbusInterface, &org::freedesktop::Notifications::ActionInvoked, this, &NotifyByPopup::onNotificationActionInvoked); | ||
broulik: Previously it would accept the signal from any service which I find odd, though. Could you… | |||||
git history until the frameworks split isn't really telling, I didn't bother looking further back nicolasfella: git history until the frameworks split isn't really telling, I didn't bother looking further… | |||||
116 | QString::fromLatin1(dbusPath), | 110 | | ||
117 | QString::fromLatin1(dbusInterfaceName), | 111 | connect(&d->dbusInterface, &org::freedesktop::Notifications::NotificationClosed, this, &NotifyByPopup::onNotificationClosed); | ||
118 | QStringLiteral("ActionInvoked"), | | |||
119 | this, | | |||
120 | SLOT(onNotificationActionInvoked(uint,QString))); | | |||
121 | if (!connected) { | | |||
122 | qCWarning(LOG_KNOTIFICATIONS) << "warning: failed to connect to ActionInvoked dbus signal"; | | |||
123 | } | | |||
124 | | ||||
125 | connected = QDBusConnection::sessionBus().connect(QString(), // from any service | | |||
126 | QString::fromLatin1(dbusPath), | | |||
127 | QString::fromLatin1(dbusInterfaceName), | | |||
128 | QStringLiteral("NotificationClosed"), | | |||
129 | this, | | |||
130 | SLOT(onNotificationClosed(uint,uint))); | | |||
131 | if (!connected) { | | |||
132 | qCWarning(LOG_KNOTIFICATIONS) << "warning: failed to connect to NotificationClosed dbus signal"; | | |||
133 | } | | |||
134 | } | 112 | } | ||
135 | 113 | | |||
136 | 114 | | |||
137 | NotifyByPopup::~NotifyByPopup() | 115 | NotifyByPopup::~NotifyByPopup() | ||
138 | { | 116 | { | ||
139 | delete d; | 117 | delete d; | ||
140 | } | 118 | } | ||
141 | 119 | | |||
Show All 37 Lines | |||||
179 | { | 157 | { | ||
180 | uint id = d->notifications.key(notification, 0); | 158 | uint id = d->notifications.key(notification, 0); | ||
181 | 159 | | |||
182 | if (id == 0) { | 160 | if (id == 0) { | ||
183 | qCDebug(LOG_KNOTIFICATIONS) << "not found dbus id to close" << notification->id(); | 161 | qCDebug(LOG_KNOTIFICATIONS) << "not found dbus id to close" << notification->id(); | ||
184 | return; | 162 | return; | ||
185 | } | 163 | } | ||
186 | 164 | | |||
187 | QDBusMessage m = QDBusMessage::createMethodCall(QString::fromLatin1(dbusServiceName), QString::fromLatin1(dbusPath), | 165 | d->dbusInterface.CloseNotification(id); | ||
188 | QString::fromLatin1(dbusInterfaceName), QStringLiteral("CloseNotification")); | | |||
189 | QList<QVariant> args; | | |||
190 | args.append(id); | | |||
191 | m.setArguments(args); | | |||
192 | | ||||
193 | // send(..) does not block | | |||
194 | bool queued = QDBusConnection::sessionBus().send(m); | | |||
195 | | ||||
196 | if (!queued) { | | |||
197 | qCWarning(LOG_KNOTIFICATIONS) << "Failed to queue dbus message for closing a notification"; | | |||
198 | } | | |||
199 | 166 | | |||
200 | QMutableListIterator<QPair<KNotification*, KNotifyConfig> > iter(d->notificationQueue); | 167 | QMutableListIterator<QPair<KNotification*, KNotifyConfig> > iter(d->notificationQueue); | ||
201 | while (iter.hasNext()) { | 168 | while (iter.hasNext()) { | ||
202 | auto &item = iter.next(); | 169 | auto &item = iter.next(); | ||
203 | if (item.first == notification) { | 170 | if (item.first == notification) { | ||
204 | iter.remove(); | 171 | iter.remove(); | ||
205 | } | 172 | } | ||
206 | } | 173 | } | ||
Show All 35 Lines | 204 | if (n) { | |||
242 | // Therefore we close the KNotification completely after closing | 209 | // Therefore we close the KNotification completely after closing | ||
243 | // the popup, but only if the reason is 2, which means "user closed" | 210 | // the popup, but only if the reason is 2, which means "user closed" | ||
244 | if (reason == 2) { | 211 | if (reason == 2) { | ||
245 | n->close(); | 212 | n->close(); | ||
246 | } | 213 | } | ||
247 | } | 214 | } | ||
248 | } | 215 | } | ||
249 | 216 | | |||
250 | void NotifyByPopup::onServerReply(QDBusPendingCallWatcher *watcher) | | |||
251 | { | | |||
252 | // call deleteLater first, since we might return in the middle of the function | | |||
253 | watcher->deleteLater(); | | |||
254 | KNotification *notification = watcher->property("notificationObject").value<KNotification*>(); | | |||
255 | if (!notification) { | | |||
256 | qCWarning(LOG_KNOTIFICATIONS) << "Invalid notification object passed in DBus reply watcher; notification will probably break"; | | |||
257 | return; | | |||
258 | } | | |||
259 | | ||||
260 | QDBusPendingReply<uint> reply = *watcher; | | |||
261 | | ||||
262 | d->notifications.insert(reply.argumentAt<0>(), notification); | | |||
263 | } | | |||
264 | | ||||
265 | void NotifyByPopup::onServerCapabilitiesReceived(const QStringList &capabilities) | | |||
266 | { | | |||
267 | d->popupServerCapabilities = capabilities; | | |||
268 | d->dbusServiceCapCacheDirty = false; | | |||
269 | | ||||
270 | // re-run notify() on all enqueued notifications | | |||
271 | for (int i = 0, total = d->notificationQueue.size(); i < total; ++i) { | | |||
272 | notify(d->notificationQueue.at(i).first, d->notificationQueue.at(i).second); | | |||
273 | } | | |||
274 | | ||||
275 | d->notificationQueue.clear(); | | |||
276 | } | | |||
277 | | ||||
278 | void NotifyByPopupPrivate::getAppCaptionAndIconName(const KNotifyConfig ¬ifyConfig, QString *appCaption, QString *iconName) | 217 | void NotifyByPopupPrivate::getAppCaptionAndIconName(const KNotifyConfig ¬ifyConfig, QString *appCaption, QString *iconName) | ||
279 | { | 218 | { | ||
280 | KConfigGroup globalgroup(&(*notifyConfig.eventsfile), QStringLiteral("Global")); | 219 | KConfigGroup globalgroup(&(*notifyConfig.eventsfile), QStringLiteral("Global")); | ||
281 | *appCaption = globalgroup.readEntry("Name", globalgroup.readEntry("Comment", notifyConfig.appname)); | 220 | *appCaption = globalgroup.readEntry("Name", globalgroup.readEntry("Comment", notifyConfig.appname)); | ||
282 | 221 | | |||
283 | KConfigGroup eventGroup(&(*notifyConfig.eventsfile), QStringLiteral("Event/%1").arg(notifyConfig.eventid)); | 222 | KConfigGroup eventGroup(&(*notifyConfig.eventsfile), QStringLiteral("Event/%1").arg(notifyConfig.eventid)); | ||
284 | if (eventGroup.hasKey("IconName")) { | 223 | if (eventGroup.hasKey("IconName")) { | ||
285 | *iconName = eventGroup.readEntry("IconName", notifyConfig.appname); | 224 | *iconName = eventGroup.readEntry("IconName", notifyConfig.appname); | ||
Show All 9 Lines | 231 | { | |||
295 | if (update) { | 234 | if (update) { | ||
296 | if (updateId == 0) { | 235 | if (updateId == 0) { | ||
297 | // we have nothing to update; the notification we're trying to update | 236 | // we have nothing to update; the notification we're trying to update | ||
298 | // has been already closed | 237 | // has been already closed | ||
299 | return false; | 238 | return false; | ||
300 | } | 239 | } | ||
301 | } | 240 | } | ||
302 | 241 | | |||
303 | QDBusMessage dbusNotificationMessage = QDBusMessage::createMethodCall(QString::fromLatin1(dbusServiceName), QString::fromLatin1(dbusPath), QString::fromLatin1(dbusInterfaceName), QStringLiteral("Notify")); | | |||
304 | | ||||
305 | QList<QVariant> args; | | |||
306 | | ||||
307 | QString appCaption; | 242 | QString appCaption; | ||
308 | QString iconName; | 243 | QString iconName; | ||
309 | getAppCaptionAndIconName(notifyConfig_nocheck, &appCaption, &iconName); | 244 | getAppCaptionAndIconName(notifyConfig_nocheck, &appCaption, &iconName); | ||
310 | 245 | | |||
311 | //did the user override the icon name? | 246 | //did the user override the icon name? | ||
312 | if (!notification->iconName().isEmpty()) { | 247 | if (!notification->iconName().isEmpty()) { | ||
313 | iconName = notification->iconName(); | 248 | iconName = notification->iconName(); | ||
314 | } | 249 | } | ||
315 | 250 | | |||
316 | args.append(appCaption); // app_name | | |||
317 | args.append(updateId); // notification to update | | |||
318 | args.append(iconName); // app_icon | | |||
319 | | ||||
320 | QString title = notification->title().isEmpty() ? appCaption : notification->title(); | 251 | QString title = notification->title().isEmpty() ? appCaption : notification->title(); | ||
321 | QString text = notification->text(); | 252 | QString text = notification->text(); | ||
322 | 253 | | |||
323 | if (!popupServerCapabilities.contains(QLatin1String("body-markup"))) { | 254 | if (!popupServerCapabilities.contains(QLatin1String("body-markup"))) { | ||
324 | title = q->stripRichText(title); | 255 | title = q->stripRichText(title); | ||
325 | text = q->stripRichText(text); | 256 | text = q->stripRichText(text); | ||
326 | } | 257 | } | ||
327 | 258 | | |||
328 | args.append(title); // summary | | |||
329 | args.append(text); // body | | |||
330 | | ||||
331 | // freedesktop.org spec defines action list to be list like | 259 | // freedesktop.org spec defines action list to be list like | ||
332 | // (act_id1, action1, act_id2, action2, ...) | 260 | // (act_id1, action1, act_id2, action2, ...) | ||
333 | // | 261 | // | ||
334 | // assign id's to actions like it's done in fillPopup() method | 262 | // assign id's to actions like it's done in fillPopup() method | ||
335 | // (i.e. starting from 1) | 263 | // (i.e. starting from 1) | ||
336 | QStringList actionList; | 264 | QStringList actionList; | ||
337 | if (popupServerCapabilities.contains(QLatin1String("actions"))) { | 265 | if (popupServerCapabilities.contains(QLatin1String("actions"))) { | ||
338 | QString defaultAction = notification->defaultAction(); | 266 | QString defaultAction = notification->defaultAction(); | ||
339 | if (!defaultAction.isEmpty()) { | 267 | if (!defaultAction.isEmpty()) { | ||
340 | actionList.append(QStringLiteral("default")); | 268 | actionList.append(QStringLiteral("default")); | ||
341 | actionList.append(defaultAction); | 269 | actionList.append(defaultAction); | ||
342 | } | 270 | } | ||
343 | int actId = 0; | 271 | int actId = 0; | ||
344 | const auto listActions = notification->actions(); | 272 | const auto listActions = notification->actions(); | ||
345 | for (const QString &actionName : listActions) { | 273 | for (const QString &actionName : listActions) { | ||
346 | actId++; | 274 | actId++; | ||
347 | actionList.append(QString::number(actId)); | 275 | actionList.append(QString::number(actId)); | ||
348 | actionList.append(actionName); | 276 | actionList.append(actionName); | ||
349 | } | 277 | } | ||
350 | } | 278 | } | ||
351 | 279 | | |||
352 | args.append(actionList); // actions | | |||
353 | | ||||
354 | QVariantMap hintsMap; | 280 | QVariantMap hintsMap; | ||
355 | // Add the application name to the hints. | 281 | // Add the application name to the hints. | ||
356 | // According to freedesktop.org spec, the app_name is supposed to be the application's "pretty name" | 282 | // According to freedesktop.org spec, the app_name is supposed to be the application's "pretty name" | ||
357 | // but in some places it's handy to know the application name itself | 283 | // but in some places it's handy to know the application name itself | ||
358 | if (!notification->appName().isEmpty()) { | 284 | if (!notification->appName().isEmpty()) { | ||
359 | hintsMap[QStringLiteral("x-kde-appname")] = notification->appName(); | 285 | hintsMap[QStringLiteral("x-kde-appname")] = notification->appName(); | ||
360 | } | 286 | } | ||
361 | 287 | | |||
▲ Show 20 Lines • Show All 56 Lines • ▼ Show 20 Line(s) | 343 | if (!notification->pixmap().isNull()) { | |||
418 | QByteArray pixmapData; | 344 | QByteArray pixmapData; | ||
419 | QBuffer buffer(&pixmapData); | 345 | QBuffer buffer(&pixmapData); | ||
420 | buffer.open(QIODevice::WriteOnly); | 346 | buffer.open(QIODevice::WriteOnly); | ||
421 | notification->pixmap().save(&buffer, "PNG"); | 347 | notification->pixmap().save(&buffer, "PNG"); | ||
422 | buffer.close(); | 348 | buffer.close(); | ||
423 | hintsMap[QStringLiteral("image_data")] = ImageConverter::variantForImage(QImage::fromData(pixmapData)); | 349 | hintsMap[QStringLiteral("image_data")] = ImageConverter::variantForImage(QImage::fromData(pixmapData)); | ||
424 | } | 350 | } | ||
425 | 351 | | |||
426 | args.append(hintsMap); // hints | | |||
427 | | ||||
428 | // Persistent => 0 == infinite timeout | 352 | // Persistent => 0 == infinite timeout | ||
429 | // CloseOnTimeout => -1 == let the server decide | 353 | // CloseOnTimeout => -1 == let the server decide | ||
430 | int timeout = (notification->flags() & KNotification::Persistent) ? 0 : -1; | 354 | int timeout = (notification->flags() & KNotification::Persistent) ? 0 : -1; | ||
431 | 355 | | |||
432 | args.append(timeout); // expire timeout | 356 | const QDBusPendingReply<uint> reply = dbusInterface.Notify(appCaption, updateId, iconName, title, text, actionList, hintsMap, timeout); | ||
433 | | ||||
434 | dbusNotificationMessage.setArguments(args); | | |||
435 | | ||||
436 | QDBusPendingCall notificationCall = QDBusConnection::sessionBus().asyncCall(dbusNotificationMessage, -1); | | |||
437 | 357 | | |||
438 | //parent is set to the notification so that no-one ever accesses a dangling pointer on the notificationObject property | 358 | //parent is set to the notification so that no-one ever accesses a dangling pointer on the notificationObject property | ||
439 | QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(notificationCall, notification); | 359 | QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, notification); | ||
440 | watcher->setProperty("notificationObject", QVariant::fromValue<KNotification*>(notification)); | | |||
441 | 360 | | |||
442 | QObject::connect(watcher, &QDBusPendingCallWatcher::finished, | 361 | QObject::connect(watcher, &QDBusPendingCallWatcher::finished, q, [this, notification](QDBusPendingCallWatcher *watcher){ | ||
443 | q, &NotifyByPopup::onServerReply); | 362 | watcher->deleteLater(); | ||
363 | QDBusPendingReply<uint> reply = *watcher; | ||||
I think we should do an error check and whether we got the correct argument count back but we previously also didn't do it, so probably fine broulik: I think we should do an error check and whether we got the correct argument count back but we… | |||||
364 | notifications.insert(reply.argumentAt<0>(), notification); | ||||
You have watcher (which is parented to notification) and notification as contexts, but in the lambda you also access this. This looks dangerous. How about using this instead of notification as context object? broulik: You have `watcher` (which is parented to `notification`) and `notification` as contexts, but in… | |||||
365 | }); | ||||
444 | 366 | | |||
445 | return true; | 367 | return true; | ||
446 | } | 368 | } | ||
447 | 369 | | |||
448 | void NotifyByPopupPrivate::queryPopupServerCapabilities() | 370 | void NotifyByPopupPrivate::queryPopupServerCapabilities() | ||
449 | { | 371 | { | ||
450 | if (dbusServiceCapCacheDirty) { | 372 | if (!dbusServiceCapCacheDirty) { | ||
451 | QDBusMessage m = QDBusMessage::createMethodCall(QString::fromLatin1(dbusServiceName), | 373 | return; | ||
452 | QString::fromLatin1(dbusPath), | 374 | } | ||
453 | QString::fromLatin1(dbusInterfaceName), | 375 | | ||
454 | QStringLiteral("GetCapabilities")); | 376 | QDBusPendingReply<QStringList> call = dbusInterface.GetCapabilities(); | ||
broulik: This is `QDBusPendingReply<QStringList>` (or just `auto`...) | |||||
455 | 377 | | |||
456 | QDBusConnection::sessionBus().callWithCallback(m, | 378 | QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call); | ||
457 | q, | 379 | | ||
458 | SLOT(onServerCapabilitiesReceived(QStringList)), | 380 | QObject::connect(watcher, &QDBusPendingCallWatcher::finished, q, [this](QDBusPendingCallWatcher *watcher) { | ||
459 | nullptr, | 381 | watcher->deleteLater(); | ||
460 | -1); | 382 | const QDBusPendingReply<QStringList> reply = *watcher; | ||
383 | const QStringList capabilities = reply.argumentAt<0>(); | ||||
384 | popupServerCapabilities = capabilities; | ||||
385 | dbusServiceCapCacheDirty = false; | ||||
Unrelated: I was wondering, do we actually monitor the notification service changing and make them dirty again? broulik: Unrelated: I was wondering, do we actually monitor the notification service changing and make… | |||||
386 | | ||||
387 | // re-run notify() on all enqueued notifications | ||||
388 | for (const QPair<KNotification*, KNotifyConfig> ¬i : qAsConst(notificationQueue)) { | ||||
broulik: range for? | |||||
389 | q->notify(noti.first, noti.second); | ||||
461 | } | 390 | } | ||
391 | | ||||
392 | notificationQueue.clear(); | ||||
393 | }); | ||||
462 | } | 394 | } |
Previously it would accept the signal from any service which I find odd, though. Could you maybe check git logs to see if there was a reason for this?
It should survive restarts anyway and the service name is defined, so I really don't see why it used to be a blind connect.