/* SPDX-FileCopyrightText: 2019 Kai Uwe Broulik SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include "settings.h" #include #include #include #include "debug.h" #include "mirroredscreenstracker_p.h" #include "server.h" // Settings #include "badgesettings.h" #include "donotdisturbsettings.h" #include "jobsettings.h" #include "notificationsettings.h" using namespace Qt::StringLiterals; using namespace NotificationManager; class Q_DECL_HIDDEN Settings::Private { public: explicit Private(Settings *q); ~Private(); void setDirty(bool dirty); Settings::NotificationBehaviors groupBehavior(const KConfigGroup &group) const; void setGroupBehavior(KConfigGroup &group, const Settings::NotificationBehaviors &behavior); KConfigGroup servicesGroup() const; KConfigGroup applicationsGroup() const; QStringList behaviorMatchesList(const KConfigGroup &group, Settings::NotificationBehavior behavior, bool on) const; Settings *q; KSharedConfig::Ptr config; KConfigWatcher::Ptr watcher; QMetaObject::Connection watcherConnection; MirroredScreensTracker::Ptr mirroredScreensTracker; DoNotDisturbSettings dndSettings; NotificationSettings notificationSettings; JobSettings jobSettings; BadgeSettings badgeSettings; bool live = false; // set to true initially in constructor bool dirty = false; }; Settings::Private::Private(Settings *q) : q(q) { } Settings::Private::~Private() = default; void Settings::Private::setDirty(bool dirty) { if (this->dirty != dirty) { this->dirty = dirty; Q_EMIT q->dirtyChanged(); } } Settings::NotificationBehaviors Settings::Private::groupBehavior(const KConfigGroup &group) const { Settings::NotificationBehaviors behaviors; behaviors.setFlag(Settings::ShowPopups, group.readEntry("ShowPopups", true)); // show popups in dnd mode implies the show popups behaviors.setFlag(Settings::ShowPopupsInDoNotDisturbMode, behaviors.testFlag(Settings::ShowPopups) && group.readEntry("ShowPopupsInDndMode", false)); behaviors.setFlag(Settings::ShowInHistory, group.readEntry("ShowInHistory", true)); behaviors.setFlag(Settings::ShowBadges, group.readEntry("ShowBadges", true)); return behaviors; } void Settings::Private::setGroupBehavior(KConfigGroup &group, const Settings::NotificationBehaviors &behavior) { if (groupBehavior(group) == behavior) { return; } const bool showPopups = behavior.testFlag(Settings::ShowPopups); if (showPopups && !group.hasDefault("ShowPopups")) { group.revertToDefault("ShowPopups", KConfigBase::Notify); } else { group.writeEntry("ShowPopups", showPopups, KConfigBase::Notify); } const bool showPopupsInDndMode = behavior.testFlag(Settings::ShowPopupsInDoNotDisturbMode); if (!showPopupsInDndMode && !group.hasDefault("ShowPopupsInDndMode")) { group.revertToDefault("ShowPopupsInDndMode", KConfigBase::Notify); } else { group.writeEntry("ShowPopupsInDndMode", showPopupsInDndMode, KConfigBase::Notify); } const bool showInHistory = behavior.testFlag(Settings::ShowInHistory); if (showInHistory && !group.hasDefault("ShowInHistory")) { group.revertToDefault("ShowInHistory", KConfig::Notify); } else { group.writeEntry("ShowInHistory", showInHistory, KConfigBase::Notify); } const bool showBadges = behavior.testFlag(Settings::ShowBadges); if (showBadges && !group.hasDefault("ShowBadges")) { group.revertToDefault("ShowBadges", KConfigBase::Notify); } else { group.writeEntry("ShowBadges", showBadges, KConfigBase::Notify); } setDirty(true); } KConfigGroup Settings::Private::servicesGroup() const { return config->group(QStringLiteral("Services")); } KConfigGroup Settings::Private::applicationsGroup() const { return config->group(QStringLiteral("Applications")); } QStringList Settings::Private::behaviorMatchesList(const KConfigGroup &group, Settings::NotificationBehavior behavior, bool on) const { QStringList matches; const QStringList apps = group.groupList(); for (const QString &app : apps) { if (groupBehavior(group.group(app)).testFlag(behavior) == on) { matches.append(app); } } return matches; } Settings::Settings(QObject *parent) : QObject(parent) , d(new Private(this)) { d->config = KSharedConfig::openConfig(u"plasmanotifyrc"_s); setLive(true); connect(&Server::self(), &Server::inhibitedByApplicationChanged, this, &Settings::notificationsInhibitedByApplicationChanged); connect(&Server::self(), &Server::inhibitionApplicationsChanged, this, &Settings::notificationInhibitionApplicationsChanged); if (d->dndSettings.whenScreensMirrored()) { d->mirroredScreensTracker = MirroredScreensTracker::createTracker(); connect(d->mirroredScreensTracker.get(), &MirroredScreensTracker::screensMirroredChanged, this, &Settings::screensMirroredChanged); } } Settings::Settings(const KSharedConfig::Ptr &config, QObject *parent) : Settings(parent) { d->config = config; } Settings::~Settings() { d->config->markAsClean(); } Settings::NotificationBehaviors Settings::applicationBehavior(const QString &desktopEntry) const { return d->groupBehavior(d->applicationsGroup().group(desktopEntry)); } void Settings::setApplicationBehavior(const QString &desktopEntry, NotificationBehaviors behaviors) { KConfigGroup group(d->applicationsGroup().group(desktopEntry)); d->setGroupBehavior(group, behaviors); } Settings::NotificationBehaviors Settings::serviceBehavior(const QString ¬ifyRcName) const { return d->groupBehavior(d->servicesGroup().group(notifyRcName)); } void Settings::setServiceBehavior(const QString ¬ifyRcName, NotificationBehaviors behaviors) { KConfigGroup group(d->servicesGroup().group(notifyRcName)); d->setGroupBehavior(group, behaviors); } void Settings::registerKnownApplication(const QString &desktopEntry) { KService::Ptr service = KService::serviceByDesktopName(desktopEntry); if (!service) { qCDebug(NOTIFICATIONMANAGER) << "Application" << desktopEntry << "cannot be registered as seen application since there is no service for it"; return; } if (service->noDisplay()) { qCDebug(NOTIFICATIONMANAGER) << "Application" << desktopEntry << "will not be registered as seen application since it's marked as NoDisplay"; return; } if (knownApplications().contains(desktopEntry)) { return; } d->applicationsGroup().group(desktopEntry).writeEntry("Seen", true); Q_EMIT knownApplicationsChanged(); } void Settings::forgetKnownApplication(const QString &desktopEntry) { if (!knownApplications().contains(desktopEntry)) { return; } // Only remove applications that were added through registerKnownApplication if (!d->applicationsGroup().group(desktopEntry).readEntry("Seen", false)) { qCDebug(NOTIFICATIONMANAGER) << "Application" << desktopEntry << "will not be removed from seen applications since it wasn't one."; return; } d->applicationsGroup().deleteGroup(desktopEntry); Q_EMIT knownApplicationsChanged(); } void Settings::load() { d->config->markAsClean(); d->config->reparseConfiguration(); d->dndSettings.load(); d->notificationSettings.load(); d->jobSettings.load(); d->badgeSettings.load(); Q_EMIT settingsChanged(); d->setDirty(false); } void Settings::save() { d->dndSettings.save(); d->notificationSettings.save(); d->jobSettings.save(); d->badgeSettings.save(); d->config->sync(); d->setDirty(false); } void Settings::defaults() { d->dndSettings.setDefaults(); d->notificationSettings.setDefaults(); d->jobSettings.setDefaults(); d->badgeSettings.setDefaults(); Q_EMIT settingsChanged(); d->setDirty(false); } bool Settings::live() const { return d->live; } void Settings::setLive(bool live) { if (live == d->live) { return; } d->live = live; if (live) { d->watcher = KConfigWatcher::create(d->config); d->watcherConnection = connect(d->watcher.get(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) { Q_UNUSED(names); if (group.name() == QLatin1String("DoNotDisturb")) { d->dndSettings.load(); bool emitScreensMirroredChanged = false; if (d->dndSettings.whenScreensMirrored()) { if (!d->mirroredScreensTracker) { d->mirroredScreensTracker = MirroredScreensTracker::createTracker(); emitScreensMirroredChanged = d->mirroredScreensTracker->screensMirrored(); connect(d->mirroredScreensTracker.get(), &MirroredScreensTracker::screensMirroredChanged, this, &Settings::screensMirroredChanged); } } else if (d->mirroredScreensTracker) { emitScreensMirroredChanged = d->mirroredScreensTracker->screensMirrored(); d->mirroredScreensTracker.reset(); } if (emitScreensMirroredChanged) { Q_EMIT screensMirroredChanged(); } } else if (group.name() == QLatin1String("Notifications")) { d->notificationSettings.load(); } else if (group.name() == QLatin1String("Jobs")) { d->jobSettings.load(); } else if (group.name() == QLatin1String("Badges")) { d->badgeSettings.load(); } Q_EMIT settingsChanged(); }); } else { disconnect(d->watcherConnection); d->watcherConnection = QMetaObject::Connection(); d->watcher.reset(); } Q_EMIT liveChanged(); } bool Settings::dirty() const { // KConfigSkeleton doesn't write into the KConfig until calling save() // so we need to track d->config->isDirty() manually return d->dirty; } bool Settings::criticalPopupsInDoNotDisturbMode() const { return d->notificationSettings.criticalInDndMode(); } void Settings::setCriticalPopupsInDoNotDisturbMode(bool enable) { if (this->criticalPopupsInDoNotDisturbMode() == enable) { return; } d->notificationSettings.setCriticalInDndMode(enable); d->setDirty(true); } bool Settings::keepNormalAlwaysOnTop() const { return d->notificationSettings.normalAlwaysOnTop(); } void Settings::setKeepNormalAlwaysOnTop(bool enable) { if (this->keepNormalAlwaysOnTop() == enable) { return; } d->notificationSettings.setNormalAlwaysOnTop(enable); d->setDirty(true); } bool Settings::lowPriorityPopups() const { return d->notificationSettings.lowPriorityPopups(); } void Settings::setLowPriorityPopups(bool enable) { if (this->lowPriorityPopups() == enable) { return; } d->notificationSettings.setLowPriorityPopups(enable); d->setDirty(true); } bool Settings::lowPriorityHistory() const { return d->notificationSettings.lowPriorityHistory(); } void Settings::setLowPriorityHistory(bool enable) { if (this->lowPriorityHistory() == enable) { return; } d->notificationSettings.setLowPriorityHistory(enable); d->setDirty(true); } Settings::PopupPosition Settings::popupPosition() const { return static_cast(d->notificationSettings.popupPosition()); } void Settings::setPopupPosition(Settings::PopupPosition position) { if (this->popupPosition() == position) { return; } d->notificationSettings.setPopupPosition(position); d->setDirty(true); } int Settings::popupTimeout() const { return d->notificationSettings.popupTimeout(); } void Settings::setPopupTimeout(int timeout) { if (this->popupTimeout() == timeout) { return; } d->notificationSettings.setPopupTimeout(timeout); d->setDirty(true); } void Settings::resetPopupTimeout() { setPopupTimeout(d->notificationSettings.defaultPopupTimeoutValue()); } bool Settings::jobsInNotifications() const { return d->jobSettings.inNotifications(); } void Settings::setJobsInNotifications(bool enable) { if (jobsInNotifications() == enable) { return; } d->jobSettings.setInNotifications(enable); d->setDirty(true); } bool Settings::permanentJobPopups() const { return d->jobSettings.permanentPopups(); } void Settings::setPermanentJobPopups(bool enable) { if (permanentJobPopups() == enable) { return; } d->jobSettings.setPermanentPopups(enable); d->setDirty(true); } bool Settings::badgesInTaskManager() const { return d->badgeSettings.inTaskManager(); } void Settings::setBadgesInTaskManager(bool enable) { if (badgesInTaskManager() == enable) { return; } d->badgeSettings.setInTaskManager(enable); d->setDirty(true); } QStringList Settings::knownApplications() const { return d->applicationsGroup().groupList(); } QStringList Settings::popupBlacklistedApplications() const { return d->behaviorMatchesList(d->applicationsGroup(), ShowPopups, false); } QStringList Settings::popupBlacklistedServices() const { return d->behaviorMatchesList(d->servicesGroup(), ShowPopups, false); } QStringList Settings::doNotDisturbPopupWhitelistedApplications() const { return d->behaviorMatchesList(d->applicationsGroup(), ShowPopupsInDoNotDisturbMode, true); } QStringList Settings::doNotDisturbPopupWhitelistedServices() const { return d->behaviorMatchesList(d->servicesGroup(), ShowPopupsInDoNotDisturbMode, true); } QStringList Settings::historyBlacklistedApplications() const { return d->behaviorMatchesList(d->applicationsGroup(), ShowInHistory, false); } QStringList Settings::historyBlacklistedServices() const { return d->behaviorMatchesList(d->servicesGroup(), ShowInHistory, false); } QStringList Settings::badgeBlacklistedApplications() const { return d->behaviorMatchesList(d->applicationsGroup(), ShowBadges, false); } QDateTime Settings::notificationsInhibitedUntil() const { return d->dndSettings.until(); } void Settings::setNotificationsInhibitedUntil(const QDateTime &time) { d->dndSettings.setUntil(time); d->setDirty(true); } void Settings::resetNotificationsInhibitedUntil() { setNotificationsInhibitedUntil(QDateTime()); // FIXME d->dndSettings.defaultUntilValue()); } bool Settings::notificationsInhibitedByApplication() const { return Server::self().inhibitedByApplication(); } QStringList Settings::notificationInhibitionApplications() const { return Server::self().inhibitionApplications(); } QStringList Settings::notificationInhibitionReasons() const { return Server::self().inhibitionReasons(); } bool Settings::inhibitNotificationsWhenScreensMirrored() const { return d->dndSettings.whenScreensMirrored(); } void Settings::setInhibitNotificationsWhenScreensMirrored(bool inhibit) { if (inhibit == inhibitNotificationsWhenScreensMirrored()) { return; } d->dndSettings.setWhenScreensMirrored(inhibit); d->setDirty(true); } bool Settings::screensMirrored() const { return d->mirroredScreensTracker && d->mirroredScreensTracker->screensMirrored(); } void Settings::setScreensMirrored(bool mirrored) { if (mirrored) { qCWarning(NOTIFICATIONMANAGER) << "Cannot forcefully set screens mirrored"; return; } if (d->mirroredScreensTracker) { d->mirroredScreensTracker->setScreensMirrored(mirrored); } } bool Settings::inhibitNotificationsWhenScreenSharing() const { return d->dndSettings.whenScreenSharing(); } void Settings::setInhibitNotificationsWhenScreenSharing(bool inhibit) { if (inhibit == inhibitNotificationsWhenScreenSharing()) { return; } d->dndSettings.setWhenScreenSharing(inhibit); d->setDirty(true); } void Settings::revokeApplicationInhibitions() { Server::self().clearInhibitions(); } bool Settings::notificationSoundsInhibited() const { return d->dndSettings.notificationSoundsMuted(); } void Settings::setNotificationSoundsInhibited(bool inhibited) { if (inhibited == notificationSoundsInhibited()) { return; } d->dndSettings.setNotificationSoundsMuted(inhibited); d->setDirty(true); }