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 "debug_p.h"
20#include "knotification_p.h"
21#include "knotificationmanager_p.h"
22#include "knotificationreplyaction.h"
23
24#include <config-knotifications.h>
25
26#include <QGuiApplication>
27
28#include <QStringList>
29#include <QUrl>
30
31// incremental notification ID
32static int notificationIdCounter = 0;
33
34class KNotificationActionPrivate
35{
36public:
37 QString label;
38 QString id;
39};
40
41KNotificationAction::KNotificationAction(QObject *parent)
42 : QObject(parent)
43 , d(new KNotificationActionPrivate)
44{
45}
46
47KNotificationAction::KNotificationAction(const QString &label)
48 : QObject()
49 , d(new KNotificationActionPrivate)
50{
51 d->label = label;
52}
53
54KNotificationAction::~KNotificationAction()
55{
56}
57
58QString KNotificationAction::label() const
59{
60 return d->label;
61}
62
63void KNotificationAction::setLabel(const QString &label)
64{
65 if (d->label != label) {
66 d->label = label;
67 Q_EMIT labelChanged(label);
68 }
69}
70
71QString KNotificationAction::id() const
72{
73 return d->id;
74}
75
76void KNotificationAction::setId(const QString &id)
77{
78 d->id = id;
79}
80
81KNotification::KNotification(const QString &eventId, NotificationFlags flags, QObject *parent)
82 : QObject(parent)
83 , d(new Private)
84{
85 d->eventId = eventId;
86 d->flags = flags;
87 connect(sender: &d->updateTimer, signal: &QTimer::timeout, context: this, slot: &KNotification::update);
88 d->updateTimer.setSingleShot(true);
89 d->updateTimer.setInterval(100);
90 d->id = ++notificationIdCounter;
91}
92
93KNotification::~KNotification()
94{
95 if (d->ownsActions) {
96 qDeleteAll(c: d->actions);
97 delete d->defaultAction;
98 }
99
100 if (d->id >= 0) {
101 KNotificationManager::self()->close(id: d->id);
102 }
103}
104
105QString KNotification::eventId() const
106{
107 return d->eventId;
108}
109
110void KNotification::setEventId(const QString &eventId)
111{
112 if (d->eventId != eventId) {
113 d->eventId = eventId;
114 Q_EMIT eventIdChanged();
115 }
116}
117
118QString KNotification::title() const
119{
120 return d->title;
121}
122
123QString KNotification::text() const
124{
125 return d->text;
126}
127
128void KNotification::setTitle(const QString &title)
129{
130 if (title == d->title) {
131 return;
132 }
133
134 d->needUpdate = true;
135 d->title = title;
136 Q_EMIT titleChanged();
137 if (d->id >= 0) {
138 d->updateTimer.start();
139 }
140}
141
142void KNotification::setText(const QString &text)
143{
144 if (text == d->text) {
145 return;
146 }
147
148 d->needUpdate = true;
149 d->text = text;
150 Q_EMIT textChanged();
151 if (d->id >= 0) {
152 d->updateTimer.start();
153 }
154}
155
156void KNotification::setIconName(const QString &icon)
157{
158 if (icon == d->iconName) {
159 return;
160 }
161
162 d->needUpdate = true;
163 d->iconName = icon;
164 Q_EMIT iconNameChanged();
165 if (d->id >= 0) {
166 d->updateTimer.start();
167 }
168}
169
170QString KNotification::iconName() const
171{
172 return d->iconName;
173}
174
175QPixmap KNotification::pixmap() const
176{
177 return d->pixmap;
178}
179
180void KNotification::setPixmap(const QPixmap &pix)
181{
182 d->needUpdate = true;
183 d->pixmap = pix;
184 if (d->id >= 0) {
185 d->updateTimer.start();
186 }
187}
188
189QList<KNotificationAction *> KNotification::actions() const
190{
191 return d->actions;
192}
193
194void KNotification::clearActions()
195{
196 if (d->ownsActions) {
197 qDeleteAll(c: d->actions);
198 }
199 d->actions.clear();
200 d->actionIdCounter = 1;
201
202 d->needUpdate = true;
203 if (d->id >= 0) {
204 d->updateTimer.start();
205 }
206}
207
208KNotificationAction *KNotification::addAction(const QString &label)
209{
210 d->needUpdate = true;
211
212 KNotificationAction *action = new KNotificationAction(label);
213 connect(sender: action, signal: &KNotificationAction::labelChanged, context: this, slot: [this] {
214 d->needUpdate = true;
215 if (d->id >= 0) {
216 d->updateTimer.start();
217 }
218 });
219 action->setId(QString::number(d->actionIdCounter));
220 d->actionIdCounter++;
221
222 d->actions << action;
223 d->ownsActions = true;
224 Q_EMIT actionsChanged();
225
226 if (d->id >= 0) {
227 d->updateTimer.start();
228 }
229
230 return action;
231}
232
233void KNotification::setActionsQml(QList<KNotificationAction *> actions)
234{
235 if (actions == d->actions) {
236 return;
237 }
238
239 d->actions.clear();
240
241 d->needUpdate = true;
242 d->actions = actions;
243 d->ownsActions = false;
244 Q_EMIT actionsChanged();
245
246 int idCounter = 1;
247
248 for (KNotificationAction *action : std::as_const(t&: d->actions)) {
249 action->setId(QString::number(idCounter));
250 ++idCounter;
251 }
252
253 if (d->id >= 0) {
254 d->updateTimer.start();
255 }
256}
257
258KNotificationReplyAction *KNotification::replyAction() const
259{
260 return d->replyAction.get();
261}
262
263void KNotification::setReplyAction(std::unique_ptr<KNotificationReplyAction> replyAction)
264{
265 if (replyAction == d->replyAction) {
266 return;
267 }
268
269 d->needUpdate = true;
270 d->replyAction = std::move(replyAction);
271 if (d->id >= 0) {
272 d->updateTimer.start();
273 }
274}
275
276KNotificationAction *KNotification::addDefaultAction(const QString &label)
277{
278 if (d->ownsActions) {
279 delete d->defaultAction;
280 }
281
282 d->needUpdate = true;
283 d->ownsActions = true;
284 d->defaultAction = new KNotificationAction(label);
285 connect(sender: d->defaultAction, signal: &KNotificationAction::labelChanged, context: this, slot: [this] {
286 d->needUpdate = true;
287 if (d->id >= 0) {
288 d->updateTimer.start();
289 }
290 });
291
292 d->defaultAction->setId(QStringLiteral("default"));
293
294 Q_EMIT defaultActionChanged();
295 if (d->id >= 0) {
296 d->updateTimer.start();
297 }
298
299 return d->defaultAction;
300}
301
302void KNotification::setDefaultActionQml(KNotificationAction *defaultAction)
303{
304 if (defaultAction == d->defaultAction) {
305 return;
306 }
307
308 d->needUpdate = true;
309 d->defaultAction = defaultAction;
310 d->ownsActions = false;
311
312 d->defaultAction->setId(QStringLiteral("default"));
313
314 Q_EMIT defaultActionChanged();
315 if (d->id >= 0) {
316 d->updateTimer.start();
317 }
318}
319
320KNotificationAction *KNotification::defaultAction() const
321{
322 return d->defaultAction;
323}
324
325KNotification::NotificationFlags KNotification::flags() const
326{
327 return d->flags;
328}
329
330void KNotification::setFlags(const NotificationFlags &flags)
331{
332 if (d->flags == flags) {
333 return;
334 }
335
336 d->needUpdate = true;
337 d->flags = flags;
338 Q_EMIT flagsChanged();
339 if (d->id >= 0) {
340 d->updateTimer.start();
341 }
342}
343
344QString KNotification::componentName() const
345{
346 return d->componentName;
347}
348
349void KNotification::setComponentName(const QString &c)
350{
351 if (d->componentName != c) {
352 d->componentName = c;
353 Q_EMIT componentNameChanged();
354 }
355}
356
357QList<QUrl> KNotification::urls() const
358{
359 return QUrl::fromStringList(uris: d->hints[QStringLiteral("x-kde-urls")].toStringList());
360}
361
362void KNotification::setUrls(const QList<QUrl> &urls)
363{
364 setHint(QStringLiteral("x-kde-urls"), value: QUrl::toStringList(uris: urls));
365 Q_EMIT urlsChanged();
366}
367
368KNotification::Urgency KNotification::urgency() const
369{
370 return d->urgency;
371}
372
373void KNotification::setUrgency(Urgency urgency)
374{
375 if (d->urgency == urgency) {
376 return;
377 }
378
379 d->needUpdate = true;
380 d->urgency = urgency;
381 Q_EMIT urgencyChanged();
382 if (d->id >= 0) {
383 d->updateTimer.start();
384 }
385}
386
387void KNotification::activate(const QString &actionId)
388{
389 if (d->defaultAction && actionId == QLatin1String("default")) {
390 Q_EMIT d->defaultAction->activated();
391 }
392
393 for (KNotificationAction *action : std::as_const(t&: d->actions)) {
394 if (action->id() == actionId) {
395 Q_EMIT action->activated();
396 }
397 }
398}
399
400void KNotification::close()
401{
402 if (d->id >= 0) {
403 KNotificationManager::self()->close(id: d->id);
404 }
405
406 if (d->id == -1) {
407 d->id = -2;
408 Q_EMIT closed();
409 if (d->autoDelete) {
410 deleteLater();
411 } else {
412 // reset for being reused
413 d->isNew = true;
414 d->id = ++notificationIdCounter;
415 }
416 }
417}
418
419static QString defaultComponentName()
420{
421#if defined(Q_OS_ANDROID)
422 return QStringLiteral("android_defaults");
423#else
424 return QStringLiteral("plasma_workspace");
425#endif
426}
427
428KNotification *KNotification::event(const QString &eventid,
429 const QString &title,
430 const QString &text,
431 const QPixmap &pixmap,
432 const NotificationFlags &flags,
433 const QString &componentName)
434{
435 KNotification *notify = new KNotification(eventid, flags);
436 notify->setTitle(title);
437 notify->setText(text);
438 notify->setPixmap(pixmap);
439 notify->setComponentName((flags & DefaultEvent) ? defaultComponentName() : componentName);
440
441 QTimer::singleShot(interval: 0, receiver: notify, slot: &KNotification::sendEvent);
442
443 return notify;
444}
445
446KNotification *
447KNotification::event(const QString &eventid, const QString &text, const QPixmap &pixmap, const NotificationFlags &flags, const QString &componentName)
448{
449 return event(eventid, title: QString(), text, pixmap, flags, componentName);
450}
451
452KNotification *KNotification::event(StandardEvent eventid, const QString &title, const QString &text, const QPixmap &pixmap, const NotificationFlags &flags)
453{
454 return event(eventid: standardEventToEventId(event: eventid), title, text, pixmap, flags: flags | DefaultEvent);
455}
456
457KNotification *KNotification::event(StandardEvent eventid, const QString &text, const QPixmap &pixmap, const NotificationFlags &flags)
458{
459 return event(eventid, title: QString(), text, pixmap, flags);
460}
461
462KNotification *KNotification::event(const QString &eventid,
463 const QString &title,
464 const QString &text,
465 const QString &iconName,
466 const NotificationFlags &flags,
467 const QString &componentName)
468{
469 KNotification *notify = new KNotification(eventid, flags);
470 notify->setTitle(title);
471 notify->setText(text);
472 notify->setIconName(iconName);
473 notify->setComponentName((flags & DefaultEvent) ? defaultComponentName() : componentName);
474
475 QTimer::singleShot(interval: 0, receiver: notify, slot: &KNotification::sendEvent);
476
477 return notify;
478}
479
480KNotification *KNotification::event(StandardEvent eventid, const QString &title, const QString &text, const QString &iconName, const NotificationFlags &flags)
481{
482 return event(eventid: standardEventToEventId(event: eventid), title, text, iconName, flags: flags | DefaultEvent);
483}
484
485KNotification *KNotification::event(StandardEvent eventid, const QString &title, const QString &text, const NotificationFlags &flags)
486{
487 return event(eventid: standardEventToEventId(event: eventid), title, text, iconName: standardEventToIconName(event: eventid), flags: flags | DefaultEvent);
488}
489
490void KNotification::ref()
491{
492 d->ref++;
493}
494void KNotification::deref()
495{
496 Q_ASSERT(d->ref > 0);
497 d->ref--;
498 if (d->ref == 0) {
499 d->id = -1;
500 close();
501 }
502}
503
504void KNotification::beep(const QString &reason)
505{
506 event(QStringLiteral("beep"), text: reason, pixmap: QPixmap(), flags: CloseOnTimeout | DefaultEvent);
507}
508
509void KNotification::sendEvent()
510{
511 d->needUpdate = false;
512 if (d->isNew) {
513 d->isNew = false;
514 KNotificationManager::self()->notify(n: this);
515 } else {
516 KNotificationManager::self()->reemit(n: this);
517 }
518}
519
520int KNotification::id()
521{
522 if (!d) {
523 return -1;
524 }
525 return d->id;
526}
527
528QString KNotification::appName() const
529{
530 QString appname;
531
532 if (d->flags & DefaultEvent) {
533 appname = defaultComponentName();
534 } else if (!d->componentName.isEmpty()) {
535 appname = d->componentName;
536 } else {
537 appname = QCoreApplication::applicationName();
538 }
539
540 return appname;
541}
542
543bool KNotification::isAutoDelete() const
544{
545 return d->autoDelete;
546}
547
548void KNotification::setAutoDelete(bool autoDelete)
549{
550 if (d->autoDelete != autoDelete) {
551 d->autoDelete = autoDelete;
552 Q_EMIT autoDeleteChanged();
553 }
554}
555
556void KNotification::update()
557{
558 if (d->needUpdate) {
559 KNotificationManager::self()->update(n: this);
560 }
561}
562
563QString KNotification::standardEventToEventId(KNotification::StandardEvent event)
564{
565 QString eventId;
566 switch (event) {
567 case Warning:
568 eventId = QStringLiteral("warning");
569 break;
570 case Error:
571 eventId = QStringLiteral("fatalerror");
572 break;
573 case Catastrophe:
574 eventId = QStringLiteral("catastrophe");
575 break;
576 case Notification: // fall through
577 default:
578 eventId = QStringLiteral("notification");
579 break;
580 }
581 return eventId;
582}
583
584QString KNotification::standardEventToIconName(KNotification::StandardEvent event)
585{
586 QString iconName;
587 switch (event) {
588 case Warning:
589 iconName = QStringLiteral("dialog-warning");
590 break;
591 case Error:
592 iconName = QStringLiteral("dialog-error");
593 break;
594 case Catastrophe:
595 iconName = QStringLiteral("dialog-error");
596 break;
597 case Notification: // fall through
598 default:
599 iconName = QStringLiteral("dialog-information");
600 break;
601 }
602 return iconName;
603}
604
605void KNotification::setHint(const QString &hint, const QVariant &value)
606{
607 if (value == d->hints.value(key: hint)) {
608 return;
609 }
610
611 d->needUpdate = true;
612 d->hints[hint] = value;
613 if (d->id >= 0) {
614 d->updateTimer.start();
615 }
616 Q_EMIT hintsChanged();
617}
618
619QVariantMap KNotification::hints() const
620{
621 return d->hints;
622}
623
624void KNotification::setHints(const QVariantMap &hints)
625{
626 if (hints == d->hints) {
627 return;
628 }
629
630 d->needUpdate = true;
631 d->hints = hints;
632 if (d->id >= 0) {
633 d->updateTimer.start();
634 }
635 Q_EMIT hintsChanged();
636}
637
638QString KNotification::xdgActivationToken() const
639{
640 return d->xdgActivationToken;
641}
642
643void KNotification::setWindow(QWindow *window)
644{
645 if (window == d->window) {
646 return;
647 }
648
649 disconnect(sender: d->window, signal: &QWindow::activeChanged, receiver: this, slot: &KNotification::slotWindowActiveChanged);
650 d->window = window;
651 connect(sender: d->window, signal: &QWindow::activeChanged, context: this, slot: &KNotification::slotWindowActiveChanged);
652}
653
654void KNotification::slotWindowActiveChanged()
655{
656 if (d->window->isActive() && (d->flags & CloseWhenWindowActivated)) {
657 close();
658 }
659}
660
661QWindow *KNotification::window() const
662{
663 return d->window;
664}
665
666#include "moc_knotification.cpp"
667

source code of knotifications/src/knotification.cpp