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 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ))) { |
93 | setHint(QStringLiteral("x-kde-xdgTokenAppId" ), value: QGuiApplication::desktopFileName()); |
94 | } |
95 | } |
96 | |
97 | KNotification::~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 | |
109 | QString KNotification::eventId() const |
110 | { |
111 | return d->eventId; |
112 | } |
113 | |
114 | void KNotification::setEventId(const QString &eventId) |
115 | { |
116 | if (d->eventId != eventId) { |
117 | d->eventId = eventId; |
118 | Q_EMIT eventIdChanged(); |
119 | } |
120 | } |
121 | |
122 | QString KNotification::title() const |
123 | { |
124 | return d->title; |
125 | } |
126 | |
127 | QString KNotification::text() const |
128 | { |
129 | return d->text; |
130 | } |
131 | |
132 | void 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 | |
146 | void 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 | |
160 | void 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 | |
174 | QString KNotification::iconName() const |
175 | { |
176 | return d->iconName; |
177 | } |
178 | |
179 | QPixmap KNotification::pixmap() const |
180 | { |
181 | return d->pixmap; |
182 | } |
183 | |
184 | void 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 | |
193 | QList<KNotificationAction *> KNotification::actions() const |
194 | { |
195 | return d->actions; |
196 | } |
197 | |
198 | void 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 | |
212 | KNotificationAction *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 | |
231 | void 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 | |
256 | KNotificationReplyAction *KNotification::replyAction() const |
257 | { |
258 | return d->replyAction.get(); |
259 | } |
260 | |
261 | void 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 | |
274 | KNotificationAction *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 | |
294 | void 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 | |
312 | KNotificationAction *KNotification::defaultAction() const |
313 | { |
314 | return d->defaultAction; |
315 | } |
316 | |
317 | KNotification::NotificationFlags KNotification::flags() const |
318 | { |
319 | return d->flags; |
320 | } |
321 | |
322 | void 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 | |
336 | QString KNotification::componentName() const |
337 | { |
338 | return d->componentName; |
339 | } |
340 | |
341 | void KNotification::setComponentName(const QString &c) |
342 | { |
343 | if (d->componentName != c) { |
344 | d->componentName = c; |
345 | Q_EMIT componentNameChanged(); |
346 | } |
347 | } |
348 | |
349 | QList<QUrl> KNotification::urls() const |
350 | { |
351 | return QUrl::fromStringList(uris: d->hints[QStringLiteral("x-kde-urls" )].toStringList()); |
352 | } |
353 | |
354 | void KNotification::setUrls(const QList<QUrl> &urls) |
355 | { |
356 | setHint(QStringLiteral("x-kde-urls" ), value: QUrl::toStringList(uris: urls)); |
357 | Q_EMIT urlsChanged(); |
358 | } |
359 | |
360 | KNotification::Urgency KNotification::urgency() const |
361 | { |
362 | return d->urgency; |
363 | } |
364 | |
365 | void 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 | |
379 | void 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 | |
392 | void 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 | |
411 | static 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 | |
420 | KNotification *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 | |
438 | KNotification * |
439 | KNotification::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 | |
444 | KNotification *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 | |
449 | KNotification *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 | |
454 | KNotification *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 | |
472 | KNotification *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 | |
477 | KNotification *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 | |
482 | void KNotification::ref() |
483 | { |
484 | d->ref++; |
485 | } |
486 | void 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 | |
496 | void KNotification::beep(const QString &reason) |
497 | { |
498 | event(QStringLiteral("beep" ), text: reason, pixmap: QPixmap(), flags: CloseOnTimeout | DefaultEvent); |
499 | } |
500 | |
501 | void 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 | |
512 | int KNotification::id() |
513 | { |
514 | if (!d) { |
515 | return -1; |
516 | } |
517 | return d->id; |
518 | } |
519 | |
520 | QString 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 | |
535 | bool KNotification::isAutoDelete() const |
536 | { |
537 | return d->autoDelete; |
538 | } |
539 | |
540 | void KNotification::setAutoDelete(bool autoDelete) |
541 | { |
542 | if (d->autoDelete != autoDelete) { |
543 | d->autoDelete = autoDelete; |
544 | Q_EMIT autoDeleteChanged(); |
545 | } |
546 | } |
547 | |
548 | void KNotification::update() |
549 | { |
550 | if (d->needUpdate) { |
551 | KNotificationManager::self()->update(n: this); |
552 | } |
553 | } |
554 | |
555 | QString 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 | |
576 | QString 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 | |
597 | void 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 | |
611 | QVariantMap KNotification::hints() const |
612 | { |
613 | return d->hints; |
614 | } |
615 | |
616 | void 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 | |
630 | QString KNotification::xdgActivationToken() const |
631 | { |
632 | return d->xdgActivationToken; |
633 | } |
634 | |
635 | void 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 | |
646 | void KNotification::slotWindowActiveChanged() |
647 | { |
648 | if (d->window->isActive() && (d->flags & CloseWhenWindowActivated)) { |
649 | close(); |
650 | } |
651 | } |
652 | |
653 | QWindow *KNotification::window() const |
654 | { |
655 | return d->window; |
656 | } |
657 | |
658 | #include "moc_knotification.cpp" |
659 | |