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 |
32 | static int notificationIdCounter = 0; |
33 | |
34 | class KNotificationActionPrivate |
35 | { |
36 | public: |
37 | QString label; |
38 | QString id; |
39 | }; |
40 | |
41 | KNotificationAction::KNotificationAction(QObject *parent) |
42 | : QObject(parent) |
43 | , d(new KNotificationActionPrivate) |
44 | { |
45 | } |
46 | |
47 | KNotificationAction::KNotificationAction(const QString &label) |
48 | : QObject() |
49 | , d(new KNotificationActionPrivate) |
50 | { |
51 | d->label = label; |
52 | } |
53 | |
54 | KNotificationAction::~KNotificationAction() |
55 | { |
56 | } |
57 | |
58 | QString KNotificationAction::label() const |
59 | { |
60 | return d->label; |
61 | } |
62 | |
63 | void KNotificationAction::setLabel(const QString &label) |
64 | { |
65 | if (d->label != label) { |
66 | d->label = label; |
67 | Q_EMIT labelChanged(label); |
68 | } |
69 | } |
70 | |
71 | QString KNotificationAction::id() const |
72 | { |
73 | return d->id; |
74 | } |
75 | |
76 | void KNotificationAction::setId(const QString &id) |
77 | { |
78 | d->id = id; |
79 | } |
80 | |
81 | KNotification::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 | |
93 | KNotification::~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 | |
105 | QString KNotification::eventId() const |
106 | { |
107 | return d->eventId; |
108 | } |
109 | |
110 | void KNotification::setEventId(const QString &eventId) |
111 | { |
112 | if (d->eventId != eventId) { |
113 | d->eventId = eventId; |
114 | Q_EMIT eventIdChanged(); |
115 | } |
116 | } |
117 | |
118 | QString KNotification::title() const |
119 | { |
120 | return d->title; |
121 | } |
122 | |
123 | QString KNotification::text() const |
124 | { |
125 | return d->text; |
126 | } |
127 | |
128 | void 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 | |
142 | void 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 | |
156 | void 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 | |
170 | QString KNotification::iconName() const |
171 | { |
172 | return d->iconName; |
173 | } |
174 | |
175 | QPixmap KNotification::pixmap() const |
176 | { |
177 | return d->pixmap; |
178 | } |
179 | |
180 | void 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 | |
189 | QList<KNotificationAction *> KNotification::actions() const |
190 | { |
191 | return d->actions; |
192 | } |
193 | |
194 | void 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 | |
208 | KNotificationAction *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 | |
233 | void 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 | |
258 | KNotificationReplyAction *KNotification::replyAction() const |
259 | { |
260 | return d->replyAction.get(); |
261 | } |
262 | |
263 | void 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 | |
276 | KNotificationAction *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 | |
302 | void 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 | |
320 | KNotificationAction *KNotification::defaultAction() const |
321 | { |
322 | return d->defaultAction; |
323 | } |
324 | |
325 | KNotification::NotificationFlags KNotification::flags() const |
326 | { |
327 | return d->flags; |
328 | } |
329 | |
330 | void 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 | |
344 | QString KNotification::componentName() const |
345 | { |
346 | return d->componentName; |
347 | } |
348 | |
349 | void KNotification::setComponentName(const QString &c) |
350 | { |
351 | if (d->componentName != c) { |
352 | d->componentName = c; |
353 | Q_EMIT componentNameChanged(); |
354 | } |
355 | } |
356 | |
357 | QList<QUrl> KNotification::urls() const |
358 | { |
359 | return QUrl::fromStringList(uris: d->hints[QStringLiteral("x-kde-urls" )].toStringList()); |
360 | } |
361 | |
362 | void KNotification::setUrls(const QList<QUrl> &urls) |
363 | { |
364 | setHint(QStringLiteral("x-kde-urls" ), value: QUrl::toStringList(uris: urls)); |
365 | Q_EMIT urlsChanged(); |
366 | } |
367 | |
368 | KNotification::Urgency KNotification::urgency() const |
369 | { |
370 | return d->urgency; |
371 | } |
372 | |
373 | void 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 | |
387 | void 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 | |
400 | void 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 | |
419 | static 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 | |
428 | KNotification *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 | |
446 | KNotification * |
447 | KNotification::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 | |
452 | KNotification *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 | |
457 | KNotification *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 | |
462 | KNotification *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 | |
480 | KNotification *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 | |
485 | KNotification *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 | |
490 | void KNotification::ref() |
491 | { |
492 | d->ref++; |
493 | } |
494 | void 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 | |
504 | void KNotification::beep(const QString &reason) |
505 | { |
506 | event(QStringLiteral("beep" ), text: reason, pixmap: QPixmap(), flags: CloseOnTimeout | DefaultEvent); |
507 | } |
508 | |
509 | void 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 | |
520 | int KNotification::id() |
521 | { |
522 | if (!d) { |
523 | return -1; |
524 | } |
525 | return d->id; |
526 | } |
527 | |
528 | QString 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 | |
543 | bool KNotification::isAutoDelete() const |
544 | { |
545 | return d->autoDelete; |
546 | } |
547 | |
548 | void KNotification::setAutoDelete(bool autoDelete) |
549 | { |
550 | if (d->autoDelete != autoDelete) { |
551 | d->autoDelete = autoDelete; |
552 | Q_EMIT autoDeleteChanged(); |
553 | } |
554 | } |
555 | |
556 | void KNotification::update() |
557 | { |
558 | if (d->needUpdate) { |
559 | KNotificationManager::self()->update(n: this); |
560 | } |
561 | } |
562 | |
563 | QString 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 | |
584 | QString 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 | |
605 | void 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 | |
619 | QVariantMap KNotification::hints() const |
620 | { |
621 | return d->hints; |
622 | } |
623 | |
624 | void 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 | |
638 | QString KNotification::xdgActivationToken() const |
639 | { |
640 | return d->xdgActivationToken; |
641 | } |
642 | |
643 | void 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 | |
654 | void KNotification::slotWindowActiveChanged() |
655 | { |
656 | if (d->window->isActive() && (d->flags & CloseWhenWindowActivated)) { |
657 | close(); |
658 | } |
659 | } |
660 | |
661 | QWindow *KNotification::window() const |
662 | { |
663 | return d->window; |
664 | } |
665 | |
666 | #include "moc_knotification.cpp" |
667 | |