1 /*
2     This file is part of the KDE libraries
3     SPDX-FileCopyrightText: 2005-2006 Olivier Goffart <ogoffart at kde.org>
4     SPDX-FileCopyrightText: 2013-2014 Martin Klapetek <mklapetek@kde.org>
5 
6     code from KNotify/KNotifyClient
7     SPDX-FileCopyrightText: 1997 Christian Esken <esken@kde.org>
8     SPDX-FileCopyrightText: 2000 Charles Samuels <charles@kde.org>
9     SPDX-FileCopyrightText: 2000 Stefan Schimanski <1Stein@gmx.de>
10     SPDX-FileCopyrightText: 2000 Matthias Ettrich <ettrich@kde.org>
11     SPDX-FileCopyrightText: 2000 Waldo Bastian <bastian@kde.org>
12     SPDX-FileCopyrightText: 2000-2003 Carsten Pfeiffer <pfeiffer@kde.org>
13     SPDX-FileCopyrightText: 2005 Allan Sandfeld Jensen <kde@carewolf.com>
14 
15     SPDX-License-Identifier: LGPL-2.0-only
16 */
17 
18 #include "knotification.h"
19 #include "knotificationmanager_p.h"
20 #include "knotificationreplyaction.h"
21 
22 #include <QCoreApplication>
23 
24 #include <QStringList>
25 #ifdef QT_WIDGETS_LIB
26 #include <QTabWidget>
27 #endif
28 #include <QTimer>
29 #include <QUrl>
30 
31 // incremental notification ID
32 static int notificationIdCounter = 0;
33 
34 struct Q_DECL_HIDDEN KNotification::Private {
35     QString eventId;
36     int id = -1;
37     int ref = 0;
38 
39     QWidget *widget = nullptr;
40     QString title;
41     QString text;
42     QString iconName;
43     QString defaultAction;
44     QStringList actions;
45     std::unique_ptr<KNotificationReplyAction> replyAction;
46     QPixmap pixmap;
47     ContextList contexts;
48     NotificationFlags flags = KNotification::CloseOnTimeout;
49     QString componentName;
50     KNotification::Urgency urgency = KNotification::DefaultUrgency;
51     QVariantMap hints;
52 
53     QTimer updateTimer;
54     bool needUpdate = false;
55     bool isNew = true;
56     bool autoDelete = true;
57 
58 #if KNOTIFICATIONS_BUILD_DEPRECATED_SINCE(5, 67)
59     /**
60      * recursive function that raise the widget. @p w
61      *
62      * @see raiseWidget()
63      */
64     static void raiseWidget(QWidget *w);
65 #endif
66 };
67 
68 #if KNOTIFICATIONS_BUILD_DEPRECATED_SINCE(5, 75)
KNotification(const QString & eventId,QWidget * parent,const NotificationFlags & flags)69 KNotification::KNotification(const QString &eventId, QWidget *parent, const NotificationFlags &flags)
70     : QObject(parent)
71     , d(new Private)
72 {
73     d->eventId = eventId;
74     d->flags = flags;
75     setWidget(parent);
76     connect(&d->updateTimer, &QTimer::timeout, this, &KNotification::update);
77     d->updateTimer.setSingleShot(true);
78     d->updateTimer.setInterval(100);
79     d->id = ++notificationIdCounter;
80 }
81 #endif
82 
KNotification(const QString & eventId,const NotificationFlags & flags,QObject * parent)83 KNotification::KNotification(const QString &eventId, const NotificationFlags &flags, QObject *parent)
84     : QObject(parent)
85     , d(new Private)
86 {
87     d->eventId = eventId;
88     d->flags = flags;
89     connect(&d->updateTimer, &QTimer::timeout, this, &KNotification::update);
90     d->updateTimer.setSingleShot(true);
91     d->updateTimer.setInterval(100);
92     d->id = ++notificationIdCounter;
93 }
94 
~KNotification()95 KNotification::~KNotification()
96 {
97     if (d->id >= 0) {
98         KNotificationManager::self()->close(d->id);
99     }
100     delete d;
101 }
102 
eventId() const103 QString KNotification::eventId() const
104 {
105     return d->eventId;
106 }
107 
setEventId(const QString & eventId)108 void KNotification::setEventId(const QString &eventId)
109 {
110     if (d->eventId != eventId) {
111         d->eventId = eventId;
112         Q_EMIT eventIdChanged();
113     }
114 }
115 
title() const116 QString KNotification::title() const
117 {
118     return d->title;
119 }
120 
text() const121 QString KNotification::text() const
122 {
123     return d->text;
124 }
125 
widget() const126 QWidget *KNotification::widget() const
127 {
128     return d->widget;
129 }
130 
setWidget(QWidget * wid)131 void KNotification::setWidget(QWidget *wid)
132 {
133 #ifdef QT_WIDGETS_LIB
134     d->widget = wid;
135     //     setParent(wid);
136     if (wid && d->flags & CloseWhenWidgetActivated) {
137         wid->installEventFilter(this);
138     }
139 #endif
140 }
141 
setTitle(const QString & title)142 void KNotification::setTitle(const QString &title)
143 {
144     if (title == d->title) {
145         return;
146     }
147 
148     d->needUpdate = true;
149     d->title = title;
150     Q_EMIT titleChanged();
151     if (d->id >= 0) {
152         d->updateTimer.start();
153     }
154 }
155 
setText(const QString & text)156 void KNotification::setText(const QString &text)
157 {
158     if (text == d->text) {
159         return;
160     }
161 
162     d->needUpdate = true;
163     d->text = text;
164     Q_EMIT textChanged();
165     if (d->id >= 0) {
166         d->updateTimer.start();
167     }
168 }
169 
setIconName(const QString & icon)170 void KNotification::setIconName(const QString &icon)
171 {
172     if (icon == d->iconName) {
173         return;
174     }
175 
176     d->needUpdate = true;
177     d->iconName = icon;
178     Q_EMIT iconNameChanged();
179     if (d->id >= 0) {
180         d->updateTimer.start();
181     }
182 }
183 
iconName() const184 QString KNotification::iconName() const
185 {
186     return d->iconName;
187 }
188 
pixmap() const189 QPixmap KNotification::pixmap() const
190 {
191     return d->pixmap;
192 }
193 
setPixmap(const QPixmap & pix)194 void KNotification::setPixmap(const QPixmap &pix)
195 {
196     d->needUpdate = true;
197     d->pixmap = pix;
198     if (d->id >= 0) {
199         d->updateTimer.start();
200     }
201 }
202 
actions() const203 QStringList KNotification::actions() const
204 {
205     return d->actions;
206 }
207 
setActions(const QStringList & as)208 void KNotification::setActions(const QStringList &as)
209 {
210     if (as == d->actions) {
211         return;
212     }
213 
214     d->needUpdate = true;
215     d->actions = as;
216     Q_EMIT actionsChanged();
217     if (d->id >= 0) {
218         d->updateTimer.start();
219     }
220 }
221 
replyAction() const222 KNotificationReplyAction *KNotification::replyAction() const
223 {
224     return d->replyAction.get();
225 }
226 
setReplyAction(std::unique_ptr<KNotificationReplyAction> replyAction)227 void KNotification::setReplyAction(std::unique_ptr<KNotificationReplyAction> replyAction)
228 {
229     if (replyAction == d->replyAction) {
230         return;
231     }
232 
233     d->needUpdate = true;
234     d->replyAction = std::move(replyAction);
235     if (d->id >= 0) {
236         d->updateTimer.start();
237     }
238 }
239 
setDefaultAction(const QString & defaultAction)240 void KNotification::setDefaultAction(const QString &defaultAction)
241 {
242     if (defaultAction == d->defaultAction) {
243         return;
244     }
245 
246     d->needUpdate = true;
247     d->defaultAction = defaultAction;
248     Q_EMIT defaultActionChanged();
249     if (d->id >= 0) {
250         d->updateTimer.start();
251     }
252 }
253 
defaultAction() const254 QString KNotification::defaultAction() const
255 {
256     return d->defaultAction;
257 }
258 
contexts() const259 KNotification::ContextList KNotification::contexts() const
260 {
261     return d->contexts;
262 }
263 
setContexts(const KNotification::ContextList & contexts)264 void KNotification::setContexts(const KNotification::ContextList &contexts)
265 {
266     d->contexts = contexts;
267 }
268 
addContext(const KNotification::Context & context)269 void KNotification::addContext(const KNotification::Context &context)
270 {
271     d->contexts << context;
272 }
273 
addContext(const QString & context_key,const QString & context_value)274 void KNotification::addContext(const QString &context_key, const QString &context_value)
275 {
276     d->contexts << qMakePair(context_key, context_value);
277 }
278 
flags() const279 KNotification::NotificationFlags KNotification::flags() const
280 {
281     return d->flags;
282 }
283 
setFlags(const NotificationFlags & flags)284 void KNotification::setFlags(const NotificationFlags &flags)
285 {
286     if (d->flags == flags) {
287         return;
288     }
289 
290     d->needUpdate = true;
291     d->flags = flags;
292     Q_EMIT flagsChanged();
293     if (d->id >= 0) {
294         d->updateTimer.start();
295     }
296 }
297 
componentName() const298 QString KNotification::componentName() const
299 {
300     return d->componentName;
301 }
302 
setComponentName(const QString & c)303 void KNotification::setComponentName(const QString &c)
304 {
305     if (d->componentName != c) {
306         d->componentName = c;
307         Q_EMIT componentNameChanged();
308     }
309 }
310 
urls() const311 QList<QUrl> KNotification::urls() const
312 {
313     return QUrl::fromStringList(d->hints[QStringLiteral("x-kde-urls")].toStringList());
314 }
315 
setUrls(const QList<QUrl> & urls)316 void KNotification::setUrls(const QList<QUrl> &urls)
317 {
318     setHint(QStringLiteral("x-kde-urls"), QUrl::toStringList(urls));
319     Q_EMIT urlsChanged();
320 }
321 
urgency() const322 KNotification::Urgency KNotification::urgency() const
323 {
324     return d->urgency;
325 }
326 
setUrgency(Urgency urgency)327 void KNotification::setUrgency(Urgency urgency)
328 {
329     if (d->urgency == urgency) {
330         return;
331     }
332 
333     d->needUpdate = true;
334     d->urgency = urgency;
335     Q_EMIT urgencyChanged();
336     if (d->id >= 0) {
337         d->updateTimer.start();
338     }
339 }
340 
activate(unsigned int action)341 void KNotification::activate(unsigned int action)
342 {
343     switch (action) {
344     case 0:
345 #if KNOTIFICATIONS_BUILD_DEPRECATED_SINCE(5, 76)
346         Q_EMIT activated();
347 #endif
348         Q_EMIT defaultActivated();
349         break;
350     case 1:
351         Q_EMIT action1Activated();
352         break;
353     case 2:
354         Q_EMIT action2Activated();
355         break;
356     case 3:
357         Q_EMIT action3Activated();
358         break;
359     }
360 
361     // emitting activated() makes the Manager close all the active plugins
362     // which will deref() the KNotification object, which will result
363     // in closing the notification
364     Q_EMIT activated(action);
365 }
366 
close()367 void KNotification::close()
368 {
369     if (d->id >= 0) {
370         KNotificationManager::self()->close(d->id);
371     }
372 
373     if (d->id == -1) {
374         d->id = -2;
375         Q_EMIT closed();
376         if (d->autoDelete) {
377             deleteLater();
378         } else {
379             // reset for being reused
380             d->isNew = true;
381             d->id = ++notificationIdCounter;
382         }
383     }
384 }
385 
386 #if KNOTIFICATIONS_BUILD_DEPRECATED_SINCE(5, 67)
raiseWidget()387 void KNotification::raiseWidget()
388 {
389     if (!d->widget) {
390         return;
391     }
392 
393     Private::raiseWidget(d->widget);
394 }
395 #endif
396 
397 #if KNOTIFICATIONS_BUILD_DEPRECATED_SINCE(5, 67)
raiseWidget(QWidget * w)398 void KNotification::Private::raiseWidget(QWidget *w)
399 {
400     // TODO  this function is far from finished.
401     if (w->isTopLevel()) {
402         w->raise();
403         w->activateWindow();
404     } else {
405         QWidget *pw = w->parentWidget();
406         raiseWidget(pw);
407 
408         if (QTabWidget *tab_widget = qobject_cast<QTabWidget *>(pw)) {
409             tab_widget->setCurrentIndex(tab_widget->indexOf(w));
410         }
411     }
412 }
413 #endif
414 
defaultComponentName()415 static QString defaultComponentName()
416 {
417 #if defined(Q_OS_ANDROID)
418     return QStringLiteral("android_defaults");
419 #else
420     return QStringLiteral("plasma_workspace");
421 #endif
422 }
423 
event(const QString & eventid,const QString & title,const QString & text,const QPixmap & pixmap,QWidget * widget,const NotificationFlags & flags,const QString & componentName)424 KNotification *KNotification::event(const QString &eventid,
425                                     const QString &title,
426                                     const QString &text,
427                                     const QPixmap &pixmap,
428                                     QWidget *widget,
429                                     const NotificationFlags &flags,
430                                     const QString &componentName)
431 {
432     KNotification *notify = new KNotification(eventid, flags);
433     notify->setWidget(widget);
434     notify->setTitle(title);
435     notify->setText(text);
436     notify->setPixmap(pixmap);
437     notify->setComponentName((flags & DefaultEvent) ? defaultComponentName() : componentName);
438 
439     QTimer::singleShot(0, notify, &KNotification::sendEvent);
440 
441     return notify;
442 }
443 
event(const QString & eventid,const QString & text,const QPixmap & pixmap,QWidget * widget,const NotificationFlags & flags,const QString & componentName)444 KNotification *KNotification::event(const QString &eventid,
445                                     const QString &text,
446                                     const QPixmap &pixmap,
447                                     QWidget *widget,
448                                     const NotificationFlags &flags,
449                                     const QString &componentName)
450 {
451     return event(eventid, QString(), text, pixmap, widget, flags, componentName);
452 }
453 
454 KNotification *
event(StandardEvent eventid,const QString & title,const QString & text,const QPixmap & pixmap,QWidget * widget,const NotificationFlags & flags)455 KNotification::event(StandardEvent eventid, const QString &title, const QString &text, const QPixmap &pixmap, QWidget *widget, const NotificationFlags &flags)
456 {
457     return event(standardEventToEventId(eventid), title, text, pixmap, widget, flags | DefaultEvent);
458 }
459 
event(StandardEvent eventid,const QString & text,const QPixmap & pixmap,QWidget * widget,const NotificationFlags & flags)460 KNotification *KNotification::event(StandardEvent eventid, const QString &text, const QPixmap &pixmap, QWidget *widget, const NotificationFlags &flags)
461 {
462     return event(eventid, QString(), text, pixmap, widget, flags);
463 }
464 
event(const QString & eventid,const QString & title,const QString & text,const QString & iconName,QWidget * widget,const NotificationFlags & flags,const QString & componentName)465 KNotification *KNotification::event(const QString &eventid,
466                                     const QString &title,
467                                     const QString &text,
468                                     const QString &iconName,
469                                     QWidget *widget,
470                                     const NotificationFlags &flags,
471                                     const QString &componentName)
472 {
473     KNotification *notify = new KNotification(eventid, flags);
474     notify->setWidget(widget);
475     notify->setTitle(title);
476     notify->setText(text);
477     notify->setIconName(iconName);
478     notify->setComponentName((flags & DefaultEvent) ? defaultComponentName() : componentName);
479 
480     QTimer::singleShot(0, notify, &KNotification::sendEvent);
481 
482     return notify;
483 }
484 
485 KNotification *
event(StandardEvent eventid,const QString & title,const QString & text,const QString & iconName,QWidget * widget,const NotificationFlags & flags)486 KNotification::event(StandardEvent eventid, const QString &title, const QString &text, const QString &iconName, QWidget *widget, const NotificationFlags &flags)
487 {
488     return event(standardEventToEventId(eventid), title, text, iconName, widget, flags | DefaultEvent);
489 }
490 
event(StandardEvent eventid,const QString & title,const QString & text,QWidget * widget,const NotificationFlags & flags)491 KNotification *KNotification::event(StandardEvent eventid, const QString &title, const QString &text, QWidget *widget, const NotificationFlags &flags)
492 {
493     return event(standardEventToEventId(eventid), title, text, standardEventToIconName(eventid), widget, flags | DefaultEvent);
494 }
495 
ref()496 void KNotification::ref()
497 {
498     d->ref++;
499 }
deref()500 void KNotification::deref()
501 {
502     Q_ASSERT(d->ref > 0);
503     d->ref--;
504     if (d->ref == 0) {
505         d->id = -1;
506         close();
507     }
508 }
509 
beep(const QString & reason,QWidget * widget)510 void KNotification::beep(const QString &reason, QWidget *widget)
511 {
512     event(QStringLiteral("beep"), reason, QPixmap(), widget, CloseOnTimeout | DefaultEvent);
513 }
514 
sendEvent()515 void KNotification::sendEvent()
516 {
517     d->needUpdate = false;
518     if (d->isNew) {
519         d->isNew = false;
520         KNotificationManager::self()->notify(this);
521     } else {
522         KNotificationManager::self()->reemit(this);
523     }
524 }
525 
id()526 int KNotification::id()
527 {
528     if (!d) {
529         return -1;
530     }
531     return d->id;
532 }
533 
appName() const534 QString KNotification::appName() const
535 {
536     QString appname;
537 
538     if (d->flags & DefaultEvent) {
539         appname = defaultComponentName();
540     } else if (!d->componentName.isEmpty()) {
541         appname = d->componentName;
542     } else {
543         appname = QCoreApplication::applicationName();
544     }
545 
546     return appname;
547 }
548 
isAutoDelete() const549 bool KNotification::isAutoDelete() const
550 {
551     return d->autoDelete;
552 }
553 
setAutoDelete(bool autoDelete)554 void KNotification::setAutoDelete(bool autoDelete)
555 {
556     if (d->autoDelete != autoDelete) {
557         d->autoDelete = autoDelete;
558         Q_EMIT autoDeleteChanged();
559     }
560 }
561 
update()562 void KNotification::update()
563 {
564     if (d->needUpdate) {
565         KNotificationManager::self()->update(this);
566     }
567 }
568 
eventFilter(QObject * watched,QEvent * event)569 bool KNotification::eventFilter(QObject *watched, QEvent *event)
570 {
571 #ifdef QT_WIDGETS_LIB
572     if (watched == d->widget) {
573         if (event->type() == QEvent::WindowActivate) {
574             if (d->flags & CloseWhenWidgetActivated) {
575                 QTimer::singleShot(500, this, &KNotification::close);
576             }
577         }
578     }
579 #endif
580 
581     return false;
582 }
583 
standardEventToEventId(KNotification::StandardEvent event)584 QString KNotification::standardEventToEventId(KNotification::StandardEvent event)
585 {
586     QString eventId;
587     switch (event) {
588     case Warning:
589         eventId = QStringLiteral("warning");
590         break;
591     case Error:
592         eventId = QStringLiteral("fatalerror");
593         break;
594     case Catastrophe:
595         eventId = QStringLiteral("catastrophe");
596         break;
597     case Notification: // fall through
598     default:
599         eventId = QStringLiteral("notification");
600         break;
601     }
602     return eventId;
603 }
604 
standardEventToIconName(KNotification::StandardEvent event)605 QString KNotification::standardEventToIconName(KNotification::StandardEvent event)
606 {
607     QString iconName;
608     switch (event) {
609     case Warning:
610         iconName = QStringLiteral("dialog-warning");
611         break;
612     case Error:
613         iconName = QStringLiteral("dialog-error");
614         break;
615     case Catastrophe:
616         iconName = QStringLiteral("dialog-error");
617         break;
618     case Notification: // fall through
619     default:
620         iconName = QStringLiteral("dialog-information");
621         break;
622     }
623     return iconName;
624 }
625 
setHint(const QString & hint,const QVariant & value)626 void KNotification::setHint(const QString &hint, const QVariant &value)
627 {
628     if (value == d->hints.value(hint)) {
629         return;
630     }
631 
632     d->needUpdate = true;
633     d->hints[hint] = value;
634     if (d->id >= 0) {
635         d->updateTimer.start();
636     }
637 }
638 
hints() const639 QVariantMap KNotification::hints() const
640 {
641     return d->hints;
642 }
643