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

source code of knotifications/src/knotification.cpp