File indexing completed on 2024-04-28 16:54:38

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("Services");
0131 }
0132 
0133 KConfigGroup Settings::Private::applicationsGroup() const
0134 {
0135     return config->group("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.data(), &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.data(), &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.data(), &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::jobsInTaskManager() const
0426 {
0427     return d->jobSettings.inTaskManager();
0428 }
0429 
0430 void Settings::setJobsInTaskManager(bool enable)
0431 {
0432     if (jobsInTaskManager() == enable) {
0433         return;
0434     }
0435     d->jobSettings.setInTaskManager(enable);
0436     d->setDirty(true);
0437 }
0438 
0439 bool Settings::jobsInNotifications() const
0440 {
0441     return d->jobSettings.inNotifications();
0442 }
0443 void Settings::setJobsInNotifications(bool enable)
0444 {
0445     if (jobsInNotifications() == enable) {
0446         return;
0447     }
0448     d->jobSettings.setInNotifications(enable);
0449     d->setDirty(true);
0450 }
0451 
0452 bool Settings::permanentJobPopups() const
0453 {
0454     return d->jobSettings.permanentPopups();
0455 }
0456 
0457 void Settings::setPermanentJobPopups(bool enable)
0458 {
0459     if (permanentJobPopups() == enable) {
0460         return;
0461     }
0462     d->jobSettings.setPermanentPopups(enable);
0463     d->setDirty(true);
0464 }
0465 
0466 bool Settings::badgesInTaskManager() const
0467 {
0468     return d->badgeSettings.inTaskManager();
0469 }
0470 
0471 void Settings::setBadgesInTaskManager(bool enable)
0472 {
0473     if (badgesInTaskManager() == enable) {
0474         return;
0475     }
0476     d->badgeSettings.setInTaskManager(enable);
0477     d->setDirty(true);
0478 }
0479 
0480 QStringList Settings::knownApplications() const
0481 {
0482     return d->applicationsGroup().groupList();
0483 }
0484 
0485 QStringList Settings::popupBlacklistedApplications() const
0486 {
0487     return d->behaviorMatchesList(d->applicationsGroup(), ShowPopups, false);
0488 }
0489 
0490 QStringList Settings::popupBlacklistedServices() const
0491 {
0492     return d->behaviorMatchesList(d->servicesGroup(), ShowPopups, false);
0493 }
0494 
0495 QStringList Settings::doNotDisturbPopupWhitelistedApplications() const
0496 {
0497     return d->behaviorMatchesList(d->applicationsGroup(), ShowPopupsInDoNotDisturbMode, true);
0498 }
0499 
0500 QStringList Settings::doNotDisturbPopupWhitelistedServices() const
0501 {
0502     return d->behaviorMatchesList(d->servicesGroup(), ShowPopupsInDoNotDisturbMode, true);
0503 }
0504 
0505 QStringList Settings::historyBlacklistedApplications() const
0506 {
0507     return d->behaviorMatchesList(d->applicationsGroup(), ShowInHistory, false);
0508 }
0509 
0510 QStringList Settings::historyBlacklistedServices() const
0511 {
0512     return d->behaviorMatchesList(d->servicesGroup(), ShowInHistory, false);
0513 }
0514 
0515 QStringList Settings::badgeBlacklistedApplications() const
0516 {
0517     return d->behaviorMatchesList(d->applicationsGroup(), ShowBadges, false);
0518 }
0519 
0520 QDateTime Settings::notificationsInhibitedUntil() const
0521 {
0522     return d->dndSettings.until();
0523 }
0524 
0525 void Settings::setNotificationsInhibitedUntil(const QDateTime &time)
0526 {
0527     d->dndSettings.setUntil(time);
0528     d->setDirty(true);
0529 }
0530 
0531 void Settings::resetNotificationsInhibitedUntil()
0532 {
0533     setNotificationsInhibitedUntil(QDateTime()); // FIXME d->dndSettings.defaultUntilValue());
0534 }
0535 
0536 bool Settings::notificationsInhibitedByApplication() const
0537 {
0538     return Server::self().inhibitedByApplication();
0539 }
0540 
0541 QStringList Settings::notificationInhibitionApplications() const
0542 {
0543     return Server::self().inhibitionApplications();
0544 }
0545 
0546 QStringList Settings::notificationInhibitionReasons() const
0547 {
0548     return Server::self().inhibitionReasons();
0549 }
0550 
0551 bool Settings::inhibitNotificationsWhenScreensMirrored() const
0552 {
0553     return d->dndSettings.whenScreensMirrored();
0554 }
0555 
0556 void Settings::setInhibitNotificationsWhenScreensMirrored(bool inhibit)
0557 {
0558     if (inhibit == inhibitNotificationsWhenScreensMirrored()) {
0559         return;
0560     }
0561 
0562     d->dndSettings.setWhenScreensMirrored(inhibit);
0563     d->setDirty(true);
0564 }
0565 
0566 bool Settings::screensMirrored() const
0567 {
0568     return d->mirroredScreensTracker && d->mirroredScreensTracker->screensMirrored();
0569 }
0570 
0571 void Settings::setScreensMirrored(bool mirrored)
0572 {
0573     if (mirrored) {
0574         qCWarning(NOTIFICATIONMANAGER) << "Cannot forcefully set screens mirrored";
0575         return;
0576     }
0577 
0578     if (d->mirroredScreensTracker) {
0579         d->mirroredScreensTracker->setScreensMirrored(mirrored);
0580     }
0581 }
0582 
0583 bool Settings::inhibitNotificationsWhenScreenSharing() const
0584 {
0585     return d->dndSettings.whenScreenSharing();
0586 }
0587 
0588 void Settings::setInhibitNotificationsWhenScreenSharing(bool inhibit)
0589 {
0590     if (inhibit == inhibitNotificationsWhenScreenSharing()) {
0591         return;
0592     }
0593 
0594     d->dndSettings.setWhenScreenSharing(inhibit);
0595     d->setDirty(true);
0596 }
0597 
0598 void Settings::revokeApplicationInhibitions()
0599 {
0600     Server::self().clearInhibitions();
0601 }
0602 
0603 bool Settings::notificationSoundsInhibited() const
0604 {
0605     return d->dndSettings.notificationSoundsMuted();
0606 }
0607 
0608 void Settings::setNotificationSoundsInhibited(bool inhibited)
0609 {
0610     if (inhibited == notificationSoundsInhibited()) {
0611         return;
0612     }
0613 
0614     d->dndSettings.setNotificationSoundsMuted(inhibited);
0615     d->setDirty(true);
0616 }