File indexing completed on 2024-05-05 05:38:32

0001 /*
0002     SPDX-FileCopyrightText: 2019 Kai Uwe Broulik <kde@privat.broulik.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include "settings.h"
0008 
0009 #include <QDebug>
0010 
0011 #include <KConfigWatcher>
0012 #include <KService>
0013 
0014 #include "debug.h"
0015 #include "mirroredscreenstracker_p.h"
0016 #include "server.h"
0017 
0018 // Settings
0019 #include "badgesettings.h"
0020 #include "donotdisturbsettings.h"
0021 #include "jobsettings.h"
0022 #include "notificationsettings.h"
0023 
0024 namespace NotificationManager
0025 {
0026 constexpr const char s_configFile[] = "plasmanotifyrc";
0027 }
0028 
0029 using namespace NotificationManager;
0030 
0031 class Q_DECL_HIDDEN Settings::Private
0032 {
0033 public:
0034     explicit Private(Settings *q);
0035     ~Private();
0036 
0037     void setDirty(bool dirty);
0038 
0039     Settings::NotificationBehaviors groupBehavior(const KConfigGroup &group) const;
0040     void setGroupBehavior(KConfigGroup &group, const Settings::NotificationBehaviors &behavior);
0041 
0042     KConfigGroup servicesGroup() const;
0043     KConfigGroup applicationsGroup() const;
0044 
0045     QStringList behaviorMatchesList(const KConfigGroup &group, Settings::NotificationBehavior behavior, bool on) const;
0046 
0047     Settings *q;
0048 
0049     KSharedConfig::Ptr config;
0050 
0051     KConfigWatcher::Ptr watcher;
0052     QMetaObject::Connection watcherConnection;
0053 
0054     MirroredScreensTracker::Ptr mirroredScreensTracker;
0055 
0056     DoNotDisturbSettings dndSettings;
0057     NotificationSettings notificationSettings;
0058     JobSettings jobSettings;
0059     BadgeSettings badgeSettings;
0060 
0061     bool live = false; // set to true initially in constructor
0062     bool dirty = false;
0063 };
0064 
0065 Settings::Private::Private(Settings *q)
0066     : q(q)
0067 {
0068 }
0069 
0070 Settings::Private::~Private() = default;
0071 
0072 void Settings::Private::setDirty(bool dirty)
0073 {
0074     if (this->dirty != dirty) {
0075         this->dirty = dirty;
0076         Q_EMIT q->dirtyChanged();
0077     }
0078 }
0079 
0080 Settings::NotificationBehaviors Settings::Private::groupBehavior(const KConfigGroup &group) const
0081 {
0082     Settings::NotificationBehaviors behaviors;
0083     behaviors.setFlag(Settings::ShowPopups, group.readEntry("ShowPopups", true));
0084     // show popups in dnd mode implies the show popups
0085     behaviors.setFlag(Settings::ShowPopupsInDoNotDisturbMode, behaviors.testFlag(Settings::ShowPopups) && group.readEntry("ShowPopupsInDndMode", false));
0086     behaviors.setFlag(Settings::ShowInHistory, group.readEntry("ShowInHistory", true));
0087     behaviors.setFlag(Settings::ShowBadges, group.readEntry("ShowBadges", true));
0088     return behaviors;
0089 }
0090 
0091 void Settings::Private::setGroupBehavior(KConfigGroup &group, const Settings::NotificationBehaviors &behavior)
0092 {
0093     if (groupBehavior(group) == behavior) {
0094         return;
0095     }
0096 
0097     const bool showPopups = behavior.testFlag(Settings::ShowPopups);
0098     if (showPopups && !group.hasDefault("ShowPopups")) {
0099         group.revertToDefault("ShowPopups", KConfigBase::Notify);
0100     } else {
0101         group.writeEntry("ShowPopups", showPopups, KConfigBase::Notify);
0102     }
0103 
0104     const bool showPopupsInDndMode = behavior.testFlag(Settings::ShowPopupsInDoNotDisturbMode);
0105     if (!showPopupsInDndMode && !group.hasDefault("ShowPopupsInDndMode")) {
0106         group.revertToDefault("ShowPopupsInDndMode", KConfigBase::Notify);
0107     } else {
0108         group.writeEntry("ShowPopupsInDndMode", showPopupsInDndMode, KConfigBase::Notify);
0109     }
0110 
0111     const bool showInHistory = behavior.testFlag(Settings::ShowInHistory);
0112     if (showInHistory && !group.hasDefault("ShowInHistory")) {
0113         group.revertToDefault("ShowInHistory", KConfig::Notify);
0114     } else {
0115         group.writeEntry("ShowInHistory", showInHistory, KConfigBase::Notify);
0116     }
0117 
0118     const bool showBadges = behavior.testFlag(Settings::ShowBadges);
0119     if (showBadges && !group.hasDefault("ShowBadges")) {
0120         group.revertToDefault("ShowBadges", KConfigBase::Notify);
0121     } else {
0122         group.writeEntry("ShowBadges", showBadges, KConfigBase::Notify);
0123     }
0124 
0125     setDirty(true);
0126 }
0127 
0128 KConfigGroup Settings::Private::servicesGroup() const
0129 {
0130     return config->group(QStringLiteral("Services"));
0131 }
0132 
0133 KConfigGroup Settings::Private::applicationsGroup() const
0134 {
0135     return config->group(QStringLiteral("Applications"));
0136 }
0137 
0138 QStringList Settings::Private::behaviorMatchesList(const KConfigGroup &group, Settings::NotificationBehavior behavior, bool on) const
0139 {
0140     QStringList matches;
0141 
0142     const QStringList apps = group.groupList();
0143     for (const QString &app : apps) {
0144         if (groupBehavior(group.group(app)).testFlag(behavior) == on) {
0145             matches.append(app);
0146         }
0147     }
0148 
0149     return matches;
0150 }
0151 
0152 Settings::Settings(QObject *parent)
0153     : QObject(parent)
0154     , d(new Private(this))
0155 {
0156     d->config = KSharedConfig::openConfig(s_configFile);
0157 
0158     setLive(true);
0159 
0160     connect(&Server::self(), &Server::inhibitedByApplicationChanged, this, &Settings::notificationsInhibitedByApplicationChanged);
0161     connect(&Server::self(), &Server::inhibitionApplicationsChanged, this, &Settings::notificationInhibitionApplicationsChanged);
0162 
0163     if (d->dndSettings.whenScreensMirrored()) {
0164         d->mirroredScreensTracker = MirroredScreensTracker::createTracker();
0165         connect(d->mirroredScreensTracker.get(), &MirroredScreensTracker::screensMirroredChanged, this, &Settings::screensMirroredChanged);
0166     }
0167 }
0168 
0169 Settings::Settings(const KSharedConfig::Ptr &config, QObject *parent)
0170     : Settings(parent)
0171 {
0172     d->config = config;
0173 }
0174 
0175 Settings::~Settings()
0176 {
0177     d->config->markAsClean();
0178 }
0179 
0180 Settings::NotificationBehaviors Settings::applicationBehavior(const QString &desktopEntry) const
0181 {
0182     return d->groupBehavior(d->applicationsGroup().group(desktopEntry));
0183 }
0184 
0185 void Settings::setApplicationBehavior(const QString &desktopEntry, NotificationBehaviors behaviors)
0186 {
0187     KConfigGroup group(d->applicationsGroup().group(desktopEntry));
0188     d->setGroupBehavior(group, behaviors);
0189 }
0190 
0191 Settings::NotificationBehaviors Settings::serviceBehavior(const QString &notifyRcName) const
0192 {
0193     return d->groupBehavior(d->servicesGroup().group(notifyRcName));
0194 }
0195 
0196 void Settings::setServiceBehavior(const QString &notifyRcName, NotificationBehaviors behaviors)
0197 {
0198     KConfigGroup group(d->servicesGroup().group(notifyRcName));
0199     d->setGroupBehavior(group, behaviors);
0200 }
0201 
0202 void Settings::registerKnownApplication(const QString &desktopEntry)
0203 {
0204     KService::Ptr service = KService::serviceByDesktopName(desktopEntry);
0205     if (!service) {
0206         qCDebug(NOTIFICATIONMANAGER) << "Application" << desktopEntry << "cannot be registered as seen application since there is no service for it";
0207         return;
0208     }
0209 
0210     if (service->noDisplay()) {
0211         qCDebug(NOTIFICATIONMANAGER) << "Application" << desktopEntry << "will not be registered as seen application since it's marked as NoDisplay";
0212         return;
0213     }
0214 
0215     if (knownApplications().contains(desktopEntry)) {
0216         return;
0217     }
0218 
0219     d->applicationsGroup().group(desktopEntry).writeEntry("Seen", true);
0220 
0221     Q_EMIT knownApplicationsChanged();
0222 }
0223 
0224 void Settings::forgetKnownApplication(const QString &desktopEntry)
0225 {
0226     if (!knownApplications().contains(desktopEntry)) {
0227         return;
0228     }
0229 
0230     // Only remove applications that were added through registerKnownApplication
0231     if (!d->applicationsGroup().group(desktopEntry).readEntry("Seen", false)) {
0232         qCDebug(NOTIFICATIONMANAGER) << "Application" << desktopEntry << "will not be removed from seen applications since it wasn't one.";
0233         return;
0234     }
0235 
0236     d->applicationsGroup().deleteGroup(desktopEntry);
0237 
0238     Q_EMIT knownApplicationsChanged();
0239 }
0240 
0241 void Settings::load()
0242 {
0243     d->config->markAsClean();
0244     d->config->reparseConfiguration();
0245     d->dndSettings.load();
0246     d->notificationSettings.load();
0247     d->jobSettings.load();
0248     d->badgeSettings.load();
0249     Q_EMIT settingsChanged();
0250     d->setDirty(false);
0251 }
0252 
0253 void Settings::save()
0254 {
0255     d->dndSettings.save();
0256     d->notificationSettings.save();
0257     d->jobSettings.save();
0258     d->badgeSettings.save();
0259 
0260     d->config->sync();
0261     d->setDirty(false);
0262 }
0263 
0264 void Settings::defaults()
0265 {
0266     d->dndSettings.setDefaults();
0267     d->notificationSettings.setDefaults();
0268     d->jobSettings.setDefaults();
0269     d->badgeSettings.setDefaults();
0270     Q_EMIT settingsChanged();
0271     d->setDirty(false);
0272 }
0273 
0274 bool Settings::live() const
0275 {
0276     return d->live;
0277 }
0278 
0279 void Settings::setLive(bool live)
0280 {
0281     if (live == d->live) {
0282         return;
0283     }
0284 
0285     d->live = live;
0286 
0287     if (live) {
0288         d->watcher = KConfigWatcher::create(d->config);
0289         d->watcherConnection = connect(d->watcher.get(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) {
0290             Q_UNUSED(names);
0291 
0292             if (group.name() == QLatin1String("DoNotDisturb")) {
0293                 d->dndSettings.load();
0294 
0295                 bool emitScreensMirroredChanged = false;
0296                 if (d->dndSettings.whenScreensMirrored()) {
0297                     if (!d->mirroredScreensTracker) {
0298                         d->mirroredScreensTracker = MirroredScreensTracker::createTracker();
0299                         emitScreensMirroredChanged = d->mirroredScreensTracker->screensMirrored();
0300                         connect(d->mirroredScreensTracker.get(), &MirroredScreensTracker::screensMirroredChanged, this, &Settings::screensMirroredChanged);
0301                     }
0302                 } else if (d->mirroredScreensTracker) {
0303                     emitScreensMirroredChanged = d->mirroredScreensTracker->screensMirrored();
0304                     d->mirroredScreensTracker.reset();
0305                 }
0306 
0307                 if (emitScreensMirroredChanged) {
0308                     Q_EMIT screensMirroredChanged();
0309                 }
0310             } else if (group.name() == QLatin1String("Notifications")) {
0311                 d->notificationSettings.load();
0312             } else if (group.name() == QLatin1String("Jobs")) {
0313                 d->jobSettings.load();
0314             } else if (group.name() == QLatin1String("Badges")) {
0315                 d->badgeSettings.load();
0316             }
0317 
0318             Q_EMIT settingsChanged();
0319         });
0320     } else {
0321         disconnect(d->watcherConnection);
0322         d->watcherConnection = QMetaObject::Connection();
0323         d->watcher.reset();
0324     }
0325 
0326     Q_EMIT liveChanged();
0327 }
0328 
0329 bool Settings::dirty() const
0330 {
0331     // KConfigSkeleton doesn't write into the KConfig until calling save()
0332     // so we need to track d->config->isDirty() manually
0333     return d->dirty;
0334 }
0335 
0336 bool Settings::criticalPopupsInDoNotDisturbMode() const
0337 {
0338     return d->notificationSettings.criticalInDndMode();
0339 }
0340 
0341 void Settings::setCriticalPopupsInDoNotDisturbMode(bool enable)
0342 {
0343     if (this->criticalPopupsInDoNotDisturbMode() == enable) {
0344         return;
0345     }
0346     d->notificationSettings.setCriticalInDndMode(enable);
0347     d->setDirty(true);
0348 }
0349 
0350 bool Settings::keepNormalAlwaysOnTop() const
0351 {
0352     return d->notificationSettings.normalAlwaysOnTop();
0353 }
0354 
0355 void Settings::setKeepNormalAlwaysOnTop(bool enable)
0356 {
0357     if (this->keepNormalAlwaysOnTop() == enable) {
0358         return;
0359     }
0360     d->notificationSettings.setNormalAlwaysOnTop(enable);
0361     d->setDirty(true);
0362 }
0363 
0364 bool Settings::lowPriorityPopups() const
0365 {
0366     return d->notificationSettings.lowPriorityPopups();
0367 }
0368 
0369 void Settings::setLowPriorityPopups(bool enable)
0370 {
0371     if (this->lowPriorityPopups() == enable) {
0372         return;
0373     }
0374     d->notificationSettings.setLowPriorityPopups(enable);
0375     d->setDirty(true);
0376 }
0377 
0378 bool Settings::lowPriorityHistory() const
0379 {
0380     return d->notificationSettings.lowPriorityHistory();
0381 }
0382 
0383 void Settings::setLowPriorityHistory(bool enable)
0384 {
0385     if (this->lowPriorityHistory() == enable) {
0386         return;
0387     }
0388     d->notificationSettings.setLowPriorityHistory(enable);
0389     d->setDirty(true);
0390 }
0391 
0392 Settings::PopupPosition Settings::popupPosition() const
0393 {
0394     return static_cast<Settings::PopupPosition>(d->notificationSettings.popupPosition());
0395 }
0396 
0397 void Settings::setPopupPosition(Settings::PopupPosition position)
0398 {
0399     if (this->popupPosition() == position) {
0400         return;
0401     }
0402     d->notificationSettings.setPopupPosition(position);
0403     d->setDirty(true);
0404 }
0405 
0406 int Settings::popupTimeout() const
0407 {
0408     return d->notificationSettings.popupTimeout();
0409 }
0410 
0411 void Settings::setPopupTimeout(int timeout)
0412 {
0413     if (this->popupTimeout() == timeout) {
0414         return;
0415     }
0416     d->notificationSettings.setPopupTimeout(timeout);
0417     d->setDirty(true);
0418 }
0419 
0420 void Settings::resetPopupTimeout()
0421 {
0422     setPopupTimeout(d->notificationSettings.defaultPopupTimeoutValue());
0423 }
0424 
0425 bool Settings::jobsInNotifications() const
0426 {
0427     return d->jobSettings.inNotifications();
0428 }
0429 void Settings::setJobsInNotifications(bool enable)
0430 {
0431     if (jobsInNotifications() == enable) {
0432         return;
0433     }
0434     d->jobSettings.setInNotifications(enable);
0435     d->setDirty(true);
0436 }
0437 
0438 bool Settings::permanentJobPopups() const
0439 {
0440     return d->jobSettings.permanentPopups();
0441 }
0442 
0443 void Settings::setPermanentJobPopups(bool enable)
0444 {
0445     if (permanentJobPopups() == enable) {
0446         return;
0447     }
0448     d->jobSettings.setPermanentPopups(enable);
0449     d->setDirty(true);
0450 }
0451 
0452 bool Settings::badgesInTaskManager() const
0453 {
0454     return d->badgeSettings.inTaskManager();
0455 }
0456 
0457 void Settings::setBadgesInTaskManager(bool enable)
0458 {
0459     if (badgesInTaskManager() == enable) {
0460         return;
0461     }
0462     d->badgeSettings.setInTaskManager(enable);
0463     d->setDirty(true);
0464 }
0465 
0466 QStringList Settings::knownApplications() const
0467 {
0468     return d->applicationsGroup().groupList();
0469 }
0470 
0471 QStringList Settings::popupBlacklistedApplications() const
0472 {
0473     return d->behaviorMatchesList(d->applicationsGroup(), ShowPopups, false);
0474 }
0475 
0476 QStringList Settings::popupBlacklistedServices() const
0477 {
0478     return d->behaviorMatchesList(d->servicesGroup(), ShowPopups, false);
0479 }
0480 
0481 QStringList Settings::doNotDisturbPopupWhitelistedApplications() const
0482 {
0483     return d->behaviorMatchesList(d->applicationsGroup(), ShowPopupsInDoNotDisturbMode, true);
0484 }
0485 
0486 QStringList Settings::doNotDisturbPopupWhitelistedServices() const
0487 {
0488     return d->behaviorMatchesList(d->servicesGroup(), ShowPopupsInDoNotDisturbMode, true);
0489 }
0490 
0491 QStringList Settings::historyBlacklistedApplications() const
0492 {
0493     return d->behaviorMatchesList(d->applicationsGroup(), ShowInHistory, false);
0494 }
0495 
0496 QStringList Settings::historyBlacklistedServices() const
0497 {
0498     return d->behaviorMatchesList(d->servicesGroup(), ShowInHistory, false);
0499 }
0500 
0501 QStringList Settings::badgeBlacklistedApplications() const
0502 {
0503     return d->behaviorMatchesList(d->applicationsGroup(), ShowBadges, false);
0504 }
0505 
0506 QDateTime Settings::notificationsInhibitedUntil() const
0507 {
0508     return d->dndSettings.until();
0509 }
0510 
0511 void Settings::setNotificationsInhibitedUntil(const QDateTime &time)
0512 {
0513     d->dndSettings.setUntil(time);
0514     d->setDirty(true);
0515 }
0516 
0517 void Settings::resetNotificationsInhibitedUntil()
0518 {
0519     setNotificationsInhibitedUntil(QDateTime()); // FIXME d->dndSettings.defaultUntilValue());
0520 }
0521 
0522 bool Settings::notificationsInhibitedByApplication() const
0523 {
0524     return Server::self().inhibitedByApplication();
0525 }
0526 
0527 QStringList Settings::notificationInhibitionApplications() const
0528 {
0529     return Server::self().inhibitionApplications();
0530 }
0531 
0532 QStringList Settings::notificationInhibitionReasons() const
0533 {
0534     return Server::self().inhibitionReasons();
0535 }
0536 
0537 bool Settings::inhibitNotificationsWhenScreensMirrored() const
0538 {
0539     return d->dndSettings.whenScreensMirrored();
0540 }
0541 
0542 void Settings::setInhibitNotificationsWhenScreensMirrored(bool inhibit)
0543 {
0544     if (inhibit == inhibitNotificationsWhenScreensMirrored()) {
0545         return;
0546     }
0547 
0548     d->dndSettings.setWhenScreensMirrored(inhibit);
0549     d->setDirty(true);
0550 }
0551 
0552 bool Settings::screensMirrored() const
0553 {
0554     return d->mirroredScreensTracker && d->mirroredScreensTracker->screensMirrored();
0555 }
0556 
0557 void Settings::setScreensMirrored(bool mirrored)
0558 {
0559     if (mirrored) {
0560         qCWarning(NOTIFICATIONMANAGER) << "Cannot forcefully set screens mirrored";
0561         return;
0562     }
0563 
0564     if (d->mirroredScreensTracker) {
0565         d->mirroredScreensTracker->setScreensMirrored(mirrored);
0566     }
0567 }
0568 
0569 bool Settings::inhibitNotificationsWhenScreenSharing() const
0570 {
0571     return d->dndSettings.whenScreenSharing();
0572 }
0573 
0574 void Settings::setInhibitNotificationsWhenScreenSharing(bool inhibit)
0575 {
0576     if (inhibit == inhibitNotificationsWhenScreenSharing()) {
0577         return;
0578     }
0579 
0580     d->dndSettings.setWhenScreenSharing(inhibit);
0581     d->setDirty(true);
0582 }
0583 
0584 void Settings::revokeApplicationInhibitions()
0585 {
0586     Server::self().clearInhibitions();
0587 }
0588 
0589 bool Settings::notificationSoundsInhibited() const
0590 {
0591     return d->dndSettings.notificationSoundsMuted();
0592 }
0593 
0594 void Settings::setNotificationSoundsInhibited(bool inhibited)
0595 {
0596     if (inhibited == notificationSoundsInhibited()) {
0597         return;
0598     }
0599 
0600     d->dndSettings.setNotificationSoundsMuted(inhibited);
0601     d->setDirty(true);
0602 }