File indexing completed on 2024-04-21 03:56:28

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2005-2006 Olivier Goffart <ogoffart at kde.org>
0004     SPDX-FileCopyrightText: 2013-2014 Martin Klapetek <mklapetek@kde.org>
0005 
0006     code from KNotify/KNotifyClient
0007     SPDX-FileCopyrightText: 1997 Christian Esken <esken@kde.org>
0008     SPDX-FileCopyrightText: 2000 Charles Samuels <charles@kde.org>
0009     SPDX-FileCopyrightText: 2000 Stefan Schimanski <1Stein@gmx.de>
0010     SPDX-FileCopyrightText: 2000 Matthias Ettrich <ettrich@kde.org>
0011     SPDX-FileCopyrightText: 2000 Waldo Bastian <bastian@kde.org>
0012     SPDX-FileCopyrightText: 2000-2003 Carsten Pfeiffer <pfeiffer@kde.org>
0013     SPDX-FileCopyrightText: 2005 Allan Sandfeld Jensen <kde@carewolf.com>
0014 
0015     SPDX-License-Identifier: LGPL-2.0-only
0016 */
0017 
0018 #include "knotification.h"
0019 #include "debug_p.h"
0020 #include "knotification_p.h"
0021 #include "knotificationmanager_p.h"
0022 #include "knotificationreplyaction.h"
0023 
0024 #include <config-knotifications.h>
0025 
0026 #include <QGuiApplication>
0027 
0028 #include <QStringList>
0029 #include <QUrl>
0030 
0031 // incremental notification ID
0032 static int notificationIdCounter = 0;
0033 
0034 class KNotificationActionPrivate
0035 {
0036 public:
0037     QString label;
0038     QString id;
0039 };
0040 
0041 KNotificationAction::KNotificationAction(QObject *parent)
0042     : QObject(parent)
0043     , d(new KNotificationActionPrivate)
0044 {
0045 }
0046 
0047 KNotificationAction::KNotificationAction(const QString &label)
0048     : QObject()
0049     , d(new KNotificationActionPrivate)
0050 {
0051     d->label = label;
0052 }
0053 
0054 KNotificationAction::~KNotificationAction()
0055 {
0056 }
0057 
0058 QString KNotificationAction::label() const
0059 {
0060     return d->label;
0061 }
0062 
0063 void KNotificationAction::setLabel(const QString &label)
0064 {
0065     if (d->label != label) {
0066         d->label = label;
0067         Q_EMIT labelChanged(label);
0068     }
0069 }
0070 
0071 QString KNotificationAction::id() const
0072 {
0073     return d->id;
0074 }
0075 
0076 void KNotificationAction::setId(const QString &id)
0077 {
0078     d->id = id;
0079 }
0080 
0081 KNotification::KNotification(const QString &eventId, NotificationFlags flags, QObject *parent)
0082     : QObject(parent)
0083     , d(new Private)
0084 {
0085     d->eventId = eventId;
0086     d->flags = flags;
0087     connect(&d->updateTimer, &QTimer::timeout, this, &KNotification::update);
0088     d->updateTimer.setSingleShot(true);
0089     d->updateTimer.setInterval(100);
0090     d->id = ++notificationIdCounter;
0091 
0092     if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"))) {
0093         setHint(QStringLiteral("x-kde-xdgTokenAppId"), QGuiApplication::desktopFileName());
0094     }
0095 }
0096 
0097 KNotification::~KNotification()
0098 {
0099     if (d->ownsActions) {
0100         qDeleteAll(d->actions);
0101         delete d->defaultAction;
0102     }
0103 
0104     if (d->id >= 0) {
0105         KNotificationManager::self()->close(d->id);
0106     }
0107 }
0108 
0109 QString KNotification::eventId() const
0110 {
0111     return d->eventId;
0112 }
0113 
0114 void KNotification::setEventId(const QString &eventId)
0115 {
0116     if (d->eventId != eventId) {
0117         d->eventId = eventId;
0118         Q_EMIT eventIdChanged();
0119     }
0120 }
0121 
0122 QString KNotification::title() const
0123 {
0124     return d->title;
0125 }
0126 
0127 QString KNotification::text() const
0128 {
0129     return d->text;
0130 }
0131 
0132 void KNotification::setTitle(const QString &title)
0133 {
0134     if (title == d->title) {
0135         return;
0136     }
0137 
0138     d->needUpdate = true;
0139     d->title = title;
0140     Q_EMIT titleChanged();
0141     if (d->id >= 0) {
0142         d->updateTimer.start();
0143     }
0144 }
0145 
0146 void KNotification::setText(const QString &text)
0147 {
0148     if (text == d->text) {
0149         return;
0150     }
0151 
0152     d->needUpdate = true;
0153     d->text = text;
0154     Q_EMIT textChanged();
0155     if (d->id >= 0) {
0156         d->updateTimer.start();
0157     }
0158 }
0159 
0160 void KNotification::setIconName(const QString &icon)
0161 {
0162     if (icon == d->iconName) {
0163         return;
0164     }
0165 
0166     d->needUpdate = true;
0167     d->iconName = icon;
0168     Q_EMIT iconNameChanged();
0169     if (d->id >= 0) {
0170         d->updateTimer.start();
0171     }
0172 }
0173 
0174 QString KNotification::iconName() const
0175 {
0176     return d->iconName;
0177 }
0178 
0179 QPixmap KNotification::pixmap() const
0180 {
0181     return d->pixmap;
0182 }
0183 
0184 void KNotification::setPixmap(const QPixmap &pix)
0185 {
0186     d->needUpdate = true;
0187     d->pixmap = pix;
0188     if (d->id >= 0) {
0189         d->updateTimer.start();
0190     }
0191 }
0192 
0193 QList<KNotificationAction *> KNotification::actions() const
0194 {
0195     return d->actions;
0196 }
0197 
0198 void KNotification::clearActions()
0199 {
0200     if (d->ownsActions) {
0201         qDeleteAll(d->actions);
0202     }
0203     d->actions.clear();
0204     d->actionIdCounter = 1;
0205 
0206     d->needUpdate = true;
0207     if (d->id >= 0) {
0208         d->updateTimer.start();
0209     }
0210 }
0211 
0212 KNotificationAction *KNotification::addAction(const QString &label)
0213 {
0214     d->needUpdate = true;
0215 
0216     KNotificationAction *action = new KNotificationAction(label);
0217     action->setId(QString::number(d->actionIdCounter));
0218     d->actionIdCounter++;
0219 
0220     d->actions << action;
0221     d->ownsActions = true;
0222     Q_EMIT actionsChanged();
0223 
0224     if (d->id >= 0) {
0225         d->updateTimer.start();
0226     }
0227 
0228     return action;
0229 }
0230 
0231 void KNotification::setActionsQml(QList<KNotificationAction *> actions)
0232 {
0233     if (actions == d->actions) {
0234         return;
0235     }
0236 
0237     d->actions.clear();
0238 
0239     d->needUpdate = true;
0240     d->actions = actions;
0241     d->ownsActions = false;
0242     Q_EMIT actionsChanged();
0243 
0244     int idCounter = 1;
0245 
0246     for (KNotificationAction *action : d->actions) {
0247         action->setId(QString::number(idCounter));
0248         ++idCounter;
0249     }
0250 
0251     if (d->id >= 0) {
0252         d->updateTimer.start();
0253     }
0254 }
0255 
0256 KNotificationReplyAction *KNotification::replyAction() const
0257 {
0258     return d->replyAction.get();
0259 }
0260 
0261 void KNotification::setReplyAction(std::unique_ptr<KNotificationReplyAction> replyAction)
0262 {
0263     if (replyAction == d->replyAction) {
0264         return;
0265     }
0266 
0267     d->needUpdate = true;
0268     d->replyAction = std::move(replyAction);
0269     if (d->id >= 0) {
0270         d->updateTimer.start();
0271     }
0272 }
0273 
0274 KNotificationAction *KNotification::addDefaultAction(const QString &label)
0275 {
0276     if (d->ownsActions) {
0277         delete d->defaultAction;
0278     }
0279 
0280     d->needUpdate = true;
0281     d->ownsActions = true;
0282     d->defaultAction = new KNotificationAction(label);
0283 
0284     d->defaultAction->setId(QStringLiteral("default"));
0285 
0286     Q_EMIT defaultActionChanged();
0287     if (d->id >= 0) {
0288         d->updateTimer.start();
0289     }
0290 
0291     return d->defaultAction;
0292 }
0293 
0294 void KNotification::setDefaultActionQml(KNotificationAction *defaultAction)
0295 {
0296     if (defaultAction == d->defaultAction) {
0297         return;
0298     }
0299 
0300     d->needUpdate = true;
0301     d->defaultAction = defaultAction;
0302     d->ownsActions = false;
0303 
0304     d->defaultAction->setId(QStringLiteral("default"));
0305 
0306     Q_EMIT defaultActionChanged();
0307     if (d->id >= 0) {
0308         d->updateTimer.start();
0309     }
0310 }
0311 
0312 KNotificationAction *KNotification::defaultAction() const
0313 {
0314     return d->defaultAction;
0315 }
0316 
0317 KNotification::NotificationFlags KNotification::flags() const
0318 {
0319     return d->flags;
0320 }
0321 
0322 void KNotification::setFlags(const NotificationFlags &flags)
0323 {
0324     if (d->flags == flags) {
0325         return;
0326     }
0327 
0328     d->needUpdate = true;
0329     d->flags = flags;
0330     Q_EMIT flagsChanged();
0331     if (d->id >= 0) {
0332         d->updateTimer.start();
0333     }
0334 }
0335 
0336 QString KNotification::componentName() const
0337 {
0338     return d->componentName;
0339 }
0340 
0341 void KNotification::setComponentName(const QString &c)
0342 {
0343     if (d->componentName != c) {
0344         d->componentName = c;
0345         Q_EMIT componentNameChanged();
0346     }
0347 }
0348 
0349 QList<QUrl> KNotification::urls() const
0350 {
0351     return QUrl::fromStringList(d->hints[QStringLiteral("x-kde-urls")].toStringList());
0352 }
0353 
0354 void KNotification::setUrls(const QList<QUrl> &urls)
0355 {
0356     setHint(QStringLiteral("x-kde-urls"), QUrl::toStringList(urls));
0357     Q_EMIT urlsChanged();
0358 }
0359 
0360 KNotification::Urgency KNotification::urgency() const
0361 {
0362     return d->urgency;
0363 }
0364 
0365 void KNotification::setUrgency(Urgency urgency)
0366 {
0367     if (d->urgency == urgency) {
0368         return;
0369     }
0370 
0371     d->needUpdate = true;
0372     d->urgency = urgency;
0373     Q_EMIT urgencyChanged();
0374     if (d->id >= 0) {
0375         d->updateTimer.start();
0376     }
0377 }
0378 
0379 void KNotification::activate(const QString &actionId)
0380 {
0381     if (d->defaultAction && actionId == QLatin1String("default")) {
0382         Q_EMIT d->defaultAction->activated();
0383     }
0384 
0385     for (KNotificationAction *action : d->actions) {
0386         if (action->id() == actionId) {
0387             Q_EMIT action->activated();
0388         }
0389     }
0390 }
0391 
0392 void KNotification::close()
0393 {
0394     if (d->id >= 0) {
0395         KNotificationManager::self()->close(d->id);
0396     }
0397 
0398     if (d->id == -1) {
0399         d->id = -2;
0400         Q_EMIT closed();
0401         if (d->autoDelete) {
0402             deleteLater();
0403         } else {
0404             // reset for being reused
0405             d->isNew = true;
0406             d->id = ++notificationIdCounter;
0407         }
0408     }
0409 }
0410 
0411 static QString defaultComponentName()
0412 {
0413 #if defined(Q_OS_ANDROID)
0414     return QStringLiteral("android_defaults");
0415 #else
0416     return QStringLiteral("plasma_workspace");
0417 #endif
0418 }
0419 
0420 KNotification *KNotification::event(const QString &eventid,
0421                                     const QString &title,
0422                                     const QString &text,
0423                                     const QPixmap &pixmap,
0424                                     const NotificationFlags &flags,
0425                                     const QString &componentName)
0426 {
0427     KNotification *notify = new KNotification(eventid, flags);
0428     notify->setTitle(title);
0429     notify->setText(text);
0430     notify->setPixmap(pixmap);
0431     notify->setComponentName((flags & DefaultEvent) ? defaultComponentName() : componentName);
0432 
0433     QTimer::singleShot(0, notify, &KNotification::sendEvent);
0434 
0435     return notify;
0436 }
0437 
0438 KNotification *
0439 KNotification::event(const QString &eventid, const QString &text, const QPixmap &pixmap, const NotificationFlags &flags, const QString &componentName)
0440 {
0441     return event(eventid, QString(), text, pixmap, flags, componentName);
0442 }
0443 
0444 KNotification *KNotification::event(StandardEvent eventid, const QString &title, const QString &text, const QPixmap &pixmap, const NotificationFlags &flags)
0445 {
0446     return event(standardEventToEventId(eventid), title, text, pixmap, flags | DefaultEvent);
0447 }
0448 
0449 KNotification *KNotification::event(StandardEvent eventid, const QString &text, const QPixmap &pixmap, const NotificationFlags &flags)
0450 {
0451     return event(eventid, QString(), text, pixmap, flags);
0452 }
0453 
0454 KNotification *KNotification::event(const QString &eventid,
0455                                     const QString &title,
0456                                     const QString &text,
0457                                     const QString &iconName,
0458                                     const NotificationFlags &flags,
0459                                     const QString &componentName)
0460 {
0461     KNotification *notify = new KNotification(eventid, flags);
0462     notify->setTitle(title);
0463     notify->setText(text);
0464     notify->setIconName(iconName);
0465     notify->setComponentName((flags & DefaultEvent) ? defaultComponentName() : componentName);
0466 
0467     QTimer::singleShot(0, notify, &KNotification::sendEvent);
0468 
0469     return notify;
0470 }
0471 
0472 KNotification *KNotification::event(StandardEvent eventid, const QString &title, const QString &text, const QString &iconName, const NotificationFlags &flags)
0473 {
0474     return event(standardEventToEventId(eventid), title, text, iconName, flags | DefaultEvent);
0475 }
0476 
0477 KNotification *KNotification::event(StandardEvent eventid, const QString &title, const QString &text, const NotificationFlags &flags)
0478 {
0479     return event(standardEventToEventId(eventid), title, text, standardEventToIconName(eventid), flags | DefaultEvent);
0480 }
0481 
0482 void KNotification::ref()
0483 {
0484     d->ref++;
0485 }
0486 void KNotification::deref()
0487 {
0488     Q_ASSERT(d->ref > 0);
0489     d->ref--;
0490     if (d->ref == 0) {
0491         d->id = -1;
0492         close();
0493     }
0494 }
0495 
0496 void KNotification::beep(const QString &reason)
0497 {
0498     event(QStringLiteral("beep"), reason, QPixmap(), CloseOnTimeout | DefaultEvent);
0499 }
0500 
0501 void KNotification::sendEvent()
0502 {
0503     d->needUpdate = false;
0504     if (d->isNew) {
0505         d->isNew = false;
0506         KNotificationManager::self()->notify(this);
0507     } else {
0508         KNotificationManager::self()->reemit(this);
0509     }
0510 }
0511 
0512 int KNotification::id()
0513 {
0514     if (!d) {
0515         return -1;
0516     }
0517     return d->id;
0518 }
0519 
0520 QString KNotification::appName() const
0521 {
0522     QString appname;
0523 
0524     if (d->flags & DefaultEvent) {
0525         appname = defaultComponentName();
0526     } else if (!d->componentName.isEmpty()) {
0527         appname = d->componentName;
0528     } else {
0529         appname = QCoreApplication::applicationName();
0530     }
0531 
0532     return appname;
0533 }
0534 
0535 bool KNotification::isAutoDelete() const
0536 {
0537     return d->autoDelete;
0538 }
0539 
0540 void KNotification::setAutoDelete(bool autoDelete)
0541 {
0542     if (d->autoDelete != autoDelete) {
0543         d->autoDelete = autoDelete;
0544         Q_EMIT autoDeleteChanged();
0545     }
0546 }
0547 
0548 void KNotification::update()
0549 {
0550     if (d->needUpdate) {
0551         KNotificationManager::self()->update(this);
0552     }
0553 }
0554 
0555 QString KNotification::standardEventToEventId(KNotification::StandardEvent event)
0556 {
0557     QString eventId;
0558     switch (event) {
0559     case Warning:
0560         eventId = QStringLiteral("warning");
0561         break;
0562     case Error:
0563         eventId = QStringLiteral("fatalerror");
0564         break;
0565     case Catastrophe:
0566         eventId = QStringLiteral("catastrophe");
0567         break;
0568     case Notification: // fall through
0569     default:
0570         eventId = QStringLiteral("notification");
0571         break;
0572     }
0573     return eventId;
0574 }
0575 
0576 QString KNotification::standardEventToIconName(KNotification::StandardEvent event)
0577 {
0578     QString iconName;
0579     switch (event) {
0580     case Warning:
0581         iconName = QStringLiteral("dialog-warning");
0582         break;
0583     case Error:
0584         iconName = QStringLiteral("dialog-error");
0585         break;
0586     case Catastrophe:
0587         iconName = QStringLiteral("dialog-error");
0588         break;
0589     case Notification: // fall through
0590     default:
0591         iconName = QStringLiteral("dialog-information");
0592         break;
0593     }
0594     return iconName;
0595 }
0596 
0597 void KNotification::setHint(const QString &hint, const QVariant &value)
0598 {
0599     if (value == d->hints.value(hint)) {
0600         return;
0601     }
0602 
0603     d->needUpdate = true;
0604     d->hints[hint] = value;
0605     if (d->id >= 0) {
0606         d->updateTimer.start();
0607     }
0608     Q_EMIT hintsChanged();
0609 }
0610 
0611 QVariantMap KNotification::hints() const
0612 {
0613     return d->hints;
0614 }
0615 
0616 void KNotification::setHints(const QVariantMap &hints)
0617 {
0618     if (hints == d->hints) {
0619         return;
0620     }
0621 
0622     d->needUpdate = true;
0623     d->hints = hints;
0624     if (d->id >= 0) {
0625         d->updateTimer.start();
0626     }
0627     Q_EMIT hintsChanged();
0628 }
0629 
0630 QString KNotification::xdgActivationToken() const
0631 {
0632     return d->xdgActivationToken;
0633 }
0634 
0635 void KNotification::setWindow(QWindow *window)
0636 {
0637     if (window == d->window) {
0638         return;
0639     }
0640 
0641     disconnect(d->window, &QWindow::activeChanged, this, &KNotification::slotWindowActiveChanged);
0642     d->window = window;
0643     connect(d->window, &QWindow::activeChanged, this, &KNotification::slotWindowActiveChanged);
0644 }
0645 
0646 void KNotification::slotWindowActiveChanged()
0647 {
0648     if (d->window->isActive() && (d->flags & CloseWhenWindowActivated)) {
0649         close();
0650     }
0651 }
0652 
0653 QWindow *KNotification::window() const
0654 {
0655     return d->window;
0656 }
0657 
0658 #include "moc_knotification.cpp"