Plasma-workspace

settings.cpp
1/*
2 SPDX-FileCopyrightText: 2019 Kai Uwe Broulik <kde@privat.broulik.de>
3 SPDX-FileCopyrightText: 2024 Kristen McWilliam <kmcwilliampublic@gmail.com>
4
5 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6*/
7
8#include "settings.h"
9
10#include <QDebug>
11
12#include <KConfigWatcher>
13#include <KService>
14
15#include "debug.h"
16#include "fullscreentracker_p.h"
17#include "mirroredscreenstracker_p.h"
18#include "server.h"
19
20// Settings
21#include "badgesettings.h"
22#include "donotdisturbsettings.h"
23#include "jobsettings.h"
24#include "notificationsettings.h"
25
26using namespace Qt::StringLiterals;
27using namespace NotificationManager;
28
29class Q_DECL_HIDDEN Settings::Private
30{
31public:
32 explicit Private(Settings *q);
33 ~Private();
34
35 void setDirty(bool dirty);
36
37 Settings::NotificationBehaviors groupBehavior(const KConfigGroup &group) const;
38 void setGroupBehavior(KConfigGroup &group, const Settings::NotificationBehaviors &behavior);
39
40 KConfigGroup servicesGroup() const;
41 KConfigGroup applicationsGroup() const;
42
43 QStringList behaviorMatchesList(const KConfigGroup &group, Settings::NotificationBehavior behavior, bool on) const;
44
45 Settings *const q;
46
47 KSharedConfig::Ptr config;
48
49 KConfigWatcher::Ptr watcher;
50 QMetaObject::Connection watcherConnection;
51
52 FullscreenTracker::Ptr fullscreenTracker;
53 MirroredScreensTracker::Ptr mirroredScreensTracker;
54
55 DoNotDisturbSettings dndSettings;
56 NotificationSettings notificationSettings;
57 JobSettings jobSettings;
58 BadgeSettings badgeSettings;
59
60 bool live = false; // set to true initially in constructor
61 bool dirty = false;
62};
63
64Settings::Private::Private(Settings *q)
65 : q(q)
66{
67}
68
69Settings::Private::~Private() = default;
70
71void Settings::Private::setDirty(bool dirty)
72{
73 if (this->dirty != dirty) {
74 this->dirty = dirty;
75 Q_EMIT q->dirtyChanged();
76 }
77}
78
79Settings::NotificationBehaviors Settings::Private::groupBehavior(const KConfigGroup &group) const
80{
81 Settings::NotificationBehaviors behaviors;
82 behaviors.setFlag(Settings::ShowPopups, group.readEntry("ShowPopups", true));
83 // show popups in dnd mode implies the show popups
84 behaviors.setFlag(Settings::ShowPopupsInDoNotDisturbMode, behaviors.testFlag(Settings::ShowPopups) && group.readEntry("ShowPopupsInDndMode", false));
85 behaviors.setFlag(Settings::ShowInHistory, group.readEntry("ShowInHistory", true));
86 behaviors.setFlag(Settings::ShowBadges, group.readEntry("ShowBadges", true));
87 return behaviors;
88}
89
90void Settings::Private::setGroupBehavior(KConfigGroup &group, const Settings::NotificationBehaviors &behavior)
91{
92 if (groupBehavior(group) == behavior) {
93 return;
94 }
95
96 const bool showPopups = behavior.testFlag(Settings::ShowPopups);
97 if (showPopups && !group.hasDefault("ShowPopups")) {
98 group.revertToDefault("ShowPopups", KConfigBase::Notify);
99 } else {
100 group.writeEntry("ShowPopups", showPopups, KConfigBase::Notify);
101 }
102
103 const bool showPopupsInDndMode = behavior.testFlag(Settings::ShowPopupsInDoNotDisturbMode);
104 if (!showPopupsInDndMode && !group.hasDefault("ShowPopupsInDndMode")) {
105 group.revertToDefault("ShowPopupsInDndMode", KConfigBase::Notify);
106 } else {
107 group.writeEntry("ShowPopupsInDndMode", showPopupsInDndMode, KConfigBase::Notify);
108 }
109
110 const bool showInHistory = behavior.testFlag(Settings::ShowInHistory);
111 if (showInHistory && !group.hasDefault("ShowInHistory")) {
112 group.revertToDefault("ShowInHistory", KConfig::Notify);
113 } else {
114 group.writeEntry("ShowInHistory", showInHistory, KConfigBase::Notify);
115 }
116
117 const bool showBadges = behavior.testFlag(Settings::ShowBadges);
118 if (showBadges && !group.hasDefault("ShowBadges")) {
119 group.revertToDefault("ShowBadges", KConfigBase::Notify);
120 } else {
121 group.writeEntry("ShowBadges", showBadges, KConfigBase::Notify);
122 }
123
124 setDirty(true);
125}
126
127KConfigGroup Settings::Private::servicesGroup() const
128{
129 return config->group(QStringLiteral("Services"));
130}
131
132KConfigGroup Settings::Private::applicationsGroup() const
133{
134 return config->group(QStringLiteral("Applications"));
135}
136
137QStringList Settings::Private::behaviorMatchesList(const KConfigGroup &group, Settings::NotificationBehavior behavior, bool on) const
138{
139 QStringList matches;
140
141 const QStringList apps = group.groupList();
142 for (const QString &app : apps) {
143 if (groupBehavior(group.group(app)).testFlag(behavior) == on) {
144 matches.append(app);
145 }
146 }
147
148 return matches;
149}
150
151Settings::Settings(QObject *parent)
152 : QObject(parent)
153 , d(new Private(this))
154{
155 d->config = KSharedConfig::openConfig(u"plasmanotifyrc"_s);
156
157 setLive(true);
158
159 connect(&Server::self(), &Server::inhibitedByApplicationChanged, this, &Settings::notificationsInhibitedByApplicationChanged);
160 connect(&Server::self(), &Server::inhibitionApplicationsChanged, this, &Settings::notificationInhibitionApplicationsChanged);
161
162 if (d->dndSettings.whenFullscreen()) {
163 d->fullscreenTracker = FullscreenTracker::createTracker();
164 connect(d->fullscreenTracker.get(), &FullscreenTracker::fullscreenFocusedChanged, this, &Settings::fullscreenFocusedChanged);
165 }
166
167 if (d->dndSettings.whenScreensMirrored()) {
168 d->mirroredScreensTracker = MirroredScreensTracker::createTracker();
169 connect(d->mirroredScreensTracker.get(), &MirroredScreensTracker::screensMirroredChanged, this, &Settings::screensMirroredChanged);
170 }
171}
172
173Settings::Settings(const KSharedConfig::Ptr &config, QObject *parent)
174 : Settings(parent)
175{
176 d->config = config;
177}
178
179Settings::~Settings()
180{
181 d->config->markAsClean();
182}
183
184Settings::NotificationBehaviors Settings::applicationBehavior(const QString &desktopEntry) const
185{
186 return d->groupBehavior(d->applicationsGroup().group(desktopEntry));
187}
188
189void Settings::setApplicationBehavior(const QString &desktopEntry, NotificationBehaviors behaviors)
190{
191 KConfigGroup group(d->applicationsGroup().group(desktopEntry));
192 d->setGroupBehavior(group, behaviors);
193}
194
195Settings::NotificationBehaviors Settings::serviceBehavior(const QString &notifyRcName) const
196{
197 return d->groupBehavior(d->servicesGroup().group(notifyRcName));
198}
199
200void Settings::setServiceBehavior(const QString &notifyRcName, NotificationBehaviors behaviors)
201{
202 KConfigGroup group(d->servicesGroup().group(notifyRcName));
203 d->setGroupBehavior(group, behaviors);
204}
205
206void Settings::registerKnownApplication(const QString &desktopEntry)
207{
208 KService::Ptr service = KService::serviceByDesktopName(desktopEntry);
209 if (!service) {
210 qCDebug(NOTIFICATIONMANAGER) << "Application" << desktopEntry << "cannot be registered as seen application since there is no service for it";
211 return;
212 }
213
214 if (service->noDisplay()) {
215 qCDebug(NOTIFICATIONMANAGER) << "Application" << desktopEntry << "will not be registered as seen application since it's marked as NoDisplay";
216 return;
217 }
218
219 if (knownApplications().contains(desktopEntry)) {
220 return;
221 }
222
223 d->applicationsGroup().group(desktopEntry).writeEntry("Seen", true);
224
225 Q_EMIT knownApplicationsChanged();
226}
227
228void Settings::forgetKnownApplication(const QString &desktopEntry)
229{
230 if (!knownApplications().contains(desktopEntry)) {
231 return;
232 }
233
234 // Only remove applications that were added through registerKnownApplication
235 if (!d->applicationsGroup().group(desktopEntry).readEntry("Seen", false)) {
236 qCDebug(NOTIFICATIONMANAGER) << "Application" << desktopEntry << "will not be removed from seen applications since it wasn't one.";
237 return;
238 }
239
240 d->applicationsGroup().deleteGroup(desktopEntry);
241
242 Q_EMIT knownApplicationsChanged();
243}
244
245void Settings::load()
246{
247 d->config->markAsClean();
248 d->config->reparseConfiguration();
249 d->dndSettings.load();
250 d->notificationSettings.load();
251 d->jobSettings.load();
252 d->badgeSettings.load();
253 Q_EMIT settingsChanged();
254 d->setDirty(false);
255}
256
257void Settings::save()
258{
259 d->dndSettings.save();
260 d->notificationSettings.save();
261 d->jobSettings.save();
262 d->badgeSettings.save();
263
264 d->config->sync();
265 d->setDirty(false);
266}
267
268void Settings::defaults()
269{
270 d->dndSettings.setDefaults();
271 d->notificationSettings.setDefaults();
272 d->jobSettings.setDefaults();
273 d->badgeSettings.setDefaults();
274 Q_EMIT settingsChanged();
275 d->setDirty(false);
276}
277
278bool Settings::live() const
279{
280 return d->live;
281}
282
283void Settings::setLive(bool live)
284{
285 if (live == d->live) {
286 return;
287 }
288
289 d->live = live;
290
291 if (live) {
292 d->watcher = KConfigWatcher::create(d->config);
293 d->watcherConnection = connect(d->watcher.get(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) {
294 Q_UNUSED(names);
295
296 if (group.name() == QLatin1String("DoNotDisturb")) {
297 d->dndSettings.load();
298
299 bool emitScreensMirroredChanged = false;
300 if (d->dndSettings.whenScreensMirrored()) {
301 if (!d->mirroredScreensTracker) {
302 d->mirroredScreensTracker = MirroredScreensTracker::createTracker();
303 emitScreensMirroredChanged = d->mirroredScreensTracker->screensMirrored();
304 connect(d->mirroredScreensTracker.get(), &MirroredScreensTracker::screensMirroredChanged, this, &Settings::screensMirroredChanged);
305 }
306 } else if (d->mirroredScreensTracker) {
307 emitScreensMirroredChanged = d->mirroredScreensTracker->screensMirrored();
308 d->mirroredScreensTracker.reset();
309 }
310
311 if (emitScreensMirroredChanged) {
312 Q_EMIT screensMirroredChanged();
313 }
314
315 bool emitFullscreenChanged = false;
316 if (d->dndSettings.whenFullscreen()) {
317 if (!d->fullscreenTracker) {
318 d->fullscreenTracker = FullscreenTracker::createTracker();
319 emitFullscreenChanged = d->fullscreenTracker->fullscreenFocused();
320 connect(d->fullscreenTracker.get(), &FullscreenTracker::fullscreenFocusedChanged, this, &Settings::fullscreenFocusedChanged);
321 }
322 } else if (d->fullscreenTracker) {
323 emitFullscreenChanged = d->fullscreenTracker->fullscreenFocused();
324 d->fullscreenTracker.reset();
325 }
326
327 if (emitFullscreenChanged) {
328 Q_EMIT fullscreenFocusedChanged();
329 }
330 } else if (group.name() == QLatin1String("Notifications")) {
331 d->notificationSettings.load();
332 } else if (group.name() == QLatin1String("Jobs")) {
333 d->jobSettings.load();
334 } else if (group.name() == QLatin1String("Badges")) {
335 d->badgeSettings.load();
336 }
337
338 Q_EMIT settingsChanged();
339 });
340 } else {
341 disconnect(d->watcherConnection);
342 d->watcherConnection = QMetaObject::Connection();
343 d->watcher.reset();
344 }
345
346 Q_EMIT liveChanged();
347}
348
349bool Settings::dirty() const
350{
351 // KConfigSkeleton doesn't write into the KConfig until calling save()
352 // so we need to track d->config->isDirty() manually
353 return d->dirty;
354}
355
357{
358 return d->notificationSettings.criticalInDndMode();
359}
360
361void Settings::setCriticalPopupsInDoNotDisturbMode(bool enable)
362{
363 if (this->criticalPopupsInDoNotDisturbMode() == enable) {
364 return;
365 }
366 d->notificationSettings.setCriticalInDndMode(enable);
367 d->setDirty(true);
368}
369
371{
372 return d->notificationSettings.lowPriorityPopups();
373}
374
375void Settings::setLowPriorityPopups(bool enable)
376{
377 if (this->lowPriorityPopups() == enable) {
378 return;
379 }
380 d->notificationSettings.setLowPriorityPopups(enable);
381 d->setDirty(true);
382}
383
385{
386 return d->notificationSettings.lowPriorityHistory();
387}
388
389void Settings::setLowPriorityHistory(bool enable)
390{
391 if (this->lowPriorityHistory() == enable) {
392 return;
393 }
394 d->notificationSettings.setLowPriorityHistory(enable);
395 d->setDirty(true);
396}
397
398Settings::PopupPosition Settings::popupPosition() const
399{
400 return static_cast<Settings::PopupPosition>(d->notificationSettings.popupPosition());
401}
402
403void Settings::setPopupPosition(Settings::PopupPosition position)
404{
405 if (this->popupPosition() == position) {
406 return;
407 }
408 d->notificationSettings.setPopupPosition(position);
409 d->setDirty(true);
410}
411
412int Settings::popupTimeout() const
413{
414 return d->notificationSettings.popupTimeout();
415}
416
417void Settings::setPopupTimeout(int timeout)
418{
419 if (this->popupTimeout() == timeout) {
420 return;
421 }
422 d->notificationSettings.setPopupTimeout(timeout);
423 d->setDirty(true);
424}
425
426void Settings::resetPopupTimeout()
427{
428 setPopupTimeout(d->notificationSettings.defaultPopupTimeoutValue());
429}
430
432{
433 return d->jobSettings.inNotifications();
434}
435void Settings::setJobsInNotifications(bool enable)
436{
437 if (jobsInNotifications() == enable) {
438 return;
439 }
440 d->jobSettings.setInNotifications(enable);
441 d->setDirty(true);
442}
443
445{
446 return d->jobSettings.permanentPopups();
447}
448
449void Settings::setPermanentJobPopups(bool enable)
450{
451 if (permanentJobPopups() == enable) {
452 return;
453 }
454 d->jobSettings.setPermanentPopups(enable);
455 d->setDirty(true);
456}
457
459{
460 return d->badgeSettings.inTaskManager();
461}
462
463void Settings::setBadgesInTaskManager(bool enable)
464{
465 if (badgesInTaskManager() == enable) {
466 return;
467 }
468 d->badgeSettings.setInTaskManager(enable);
469 d->setDirty(true);
470}
471
472QStringList Settings::knownApplications() const
473{
474 return d->applicationsGroup().groupList();
475}
476
478{
479 return d->behaviorMatchesList(d->applicationsGroup(), ShowPopups, false);
480}
481
482QStringList Settings::popupBlacklistedServices() const
483{
484 return d->behaviorMatchesList(d->servicesGroup(), ShowPopups, false);
485}
486
488{
489 return d->behaviorMatchesList(d->applicationsGroup(), ShowPopupsInDoNotDisturbMode, true);
490}
491
493{
494 return d->behaviorMatchesList(d->servicesGroup(), ShowPopupsInDoNotDisturbMode, true);
495}
496
498{
499 return d->behaviorMatchesList(d->applicationsGroup(), ShowInHistory, false);
500}
501
502QStringList Settings::historyBlacklistedServices() const
503{
504 return d->behaviorMatchesList(d->servicesGroup(), ShowInHistory, false);
505}
506
508{
509 return d->behaviorMatchesList(d->applicationsGroup(), ShowBadges, false);
510}
511
513{
514 return d->dndSettings.until();
515}
516
517void Settings::setNotificationsInhibitedUntil(const QDateTime &time)
518{
519 d->dndSettings.setUntil(time);
520 d->setDirty(true);
521}
522
523void Settings::resetNotificationsInhibitedUntil()
524{
525 setNotificationsInhibitedUntil(QDateTime()); // FIXME d->dndSettings.defaultUntilValue());
526}
527
529{
530 return Server::self().inhibitedByApplication();
531}
532
533QStringList Settings::notificationInhibitionApplications() const
534{
535 return Server::self().inhibitionApplications();
536}
537
538QStringList Settings::notificationInhibitionReasons() const
539{
540 return Server::self().inhibitionReasons();
541}
542
544{
545 return d->dndSettings.whenScreensMirrored();
546}
547
548void Settings::setInhibitNotificationsWhenScreensMirrored(bool inhibit)
549{
551 return;
552 }
553
554 d->dndSettings.setWhenScreensMirrored(inhibit);
555 d->setDirty(true);
556}
557
558bool Settings::screensMirrored() const
559{
560 return d->mirroredScreensTracker && d->mirroredScreensTracker->screensMirrored();
561}
562
563void Settings::setScreensMirrored(bool mirrored)
564{
565 if (mirrored) {
566 qCWarning(NOTIFICATIONMANAGER) << "Cannot forcefully set screens mirrored";
567 return;
568 }
569
570 if (d->mirroredScreensTracker) {
571 d->mirroredScreensTracker->setScreensMirrored(mirrored);
572 }
573}
574
576{
577 return d->dndSettings.whenScreenSharing();
578}
579
580void Settings::setInhibitNotificationsWhenScreenSharing(bool inhibit)
581{
582 if (inhibit == inhibitNotificationsWhenScreenSharing()) {
583 return;
584 }
585
586 d->dndSettings.setWhenScreenSharing(inhibit);
587 d->setDirty(true);
588}
589
591{
592 return d->dndSettings.whenFullscreen();
593}
594
595void Settings::setInhibitNotificationsWhenFullscreen(bool inhibit)
596{
597 if (inhibit == inhibitNotificationsWhenFullscreen()) {
598 return;
599 }
600
601 d->dndSettings.setWhenFullscreen(inhibit);
602 d->setDirty(true);
603}
604
606{
607 return d->fullscreenTracker && d->fullscreenTracker->fullscreenFocused();
608}
609
610void Settings::setFullscreenFocused(bool focused)
611{
612 if (focused) {
613 qCWarning(NOTIFICATIONMANAGER) << "Cannot forcefully set fullscreen focused";
614 return;
615 }
616
617 if (d->fullscreenTracker) {
618 d->fullscreenTracker->setFullscreenFocused(focused);
619 }
620}
621
623{
624 Server::self().clearInhibitions();
625}
626
628{
629 return d->dndSettings.notificationSoundsMuted();
630}
631
632void Settings::setNotificationSoundsInhibited(bool inhibited)
633{
634 if (inhibited == notificationSoundsInhibited()) {
635 return;
636 }
637
638 d->dndSettings.setNotificationSoundsMuted(inhibited);
639 d->setDirty(true);
640}
641
642#include "moc_settings.cpp"
KConfigGroup group(const QString &group)
QString name() const
void revertToDefault(const char *key, WriteConfigFlags pFlag=WriteConfigFlags())
bool hasDefault(const char *key) const
void writeEntry(const char *key, const char *value, WriteConfigFlags pFlags=Normal)
QString readEntry(const char *key, const char *aDefault=nullptr) const
QStringList groupList() const override
static Ptr create(const KSharedConfig::Ptr &config)
void configChanged(const KConfigGroup &group, const QByteArrayList &names)
static Ptr serviceByDesktopName(const QString &_name)
QExplicitlySharedDataPointer< KService > Ptr
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
void inhibitedByApplicationChanged(bool inhibited)
Emitted when inhibitions by application have been changed.
void inhibitionApplicationsChanged()
Emitted when the list of applications holding a notification inhibition changes.
Notification settings and state.
Definition settings.h:36
QStringList popupBlacklistedApplications
A list of desktop entries of applications for which no popups should be shown.
Definition settings.h:87
bool notificationsInhibitedByApplication
Whether an application currently requested do not disturb mode.
Definition settings.h:134
bool screensMirrored
Whether there currently are mirrored/overlapping screens.
Definition settings.h:158
bool inhibitNotificationsWhenScreensMirrored
Whether to enable do not disturb mode when screens are mirrored/overlapping.
Definition settings.h:146
bool jobsInNotifications
Whether to show application jobs as notifications.
Definition settings.h:68
QStringList badgeBlacklistedApplications
A list of desktop entries of applications which shouldn't show badges in task manager.
Definition settings.h:114
bool dirty
Whether the settings have changed and need to be saved.
Definition settings.h:217
QStringList doNotDisturbPopupWhitelistedServices
A list of notifyrc names of services for which a popup should be shown even in do not disturb mode.
Definition settings.h:100
QML_ELEMENTbool criticalPopupsInDoNotDisturbMode
Whether to show critical notification popups in do not disturb mode.
Definition settings.h:43
bool notificationSoundsInhibited
Whether notification sounds should be disabled.
Definition settings.h:200
QDateTime notificationsInhibitedUntil
The date until which do not disturb mode is enabled.
Definition settings.h:124
int popupTimeout
The default timeout for notification popups that do not have an explicit timeout set,...
Definition settings.h:63
QStringList historyBlacklistedApplications
A list of desktop entries of applications which shouldn't be shown in the history.
Definition settings.h:105
QStringList knownApplications
A list of desktop entries of applications that have been seen sending a notification.
Definition settings.h:82
bool lowPriorityHistory
Whether to add low priority notifications to the history.
Definition settings.h:51
bool live
Whether to update the properties immediately when they are changed on disk.
Definition settings.h:210
Q_INVOKABLE void revokeApplicationInhibitions()
Revoke application notification inhibitions.
Definition settings.cpp:622
QStringList popupBlacklistedServices
A list of notifyrc names of services for which no popups should be shown.
Definition settings.h:91
QStringList historyBlacklistedServices
A list of notifyrc names of services which shouldn't be shown in the history.
Definition settings.h:109
PopupPosition popupPosition
The notification popup position on screen.
Definition settings.h:57
bool inhibitNotificationsWhenFullscreen
Whether to enable do not disturb mode when a fullscreen window is focused.
Definition settings.h:174
QStringList doNotDisturbPopupWhitelistedApplications
A list of desktop entries of applications for which a popup should be shown even in do not disturb mo...
Definition settings.h:96
bool inhibitNotificationsWhenScreenSharing
Whether to enable do not disturb mode while screen sharing.
Definition settings.h:166
bool badgesInTaskManager
Whether to show notification badges (numbers in circles) in task manager.
Definition settings.h:77
bool fullscreenFocused
Whether a fullscreen window is currently focused.
Definition settings.h:186
bool permanentJobPopups
Whether application jobs stay visible for the whole duration of the job.
Definition settings.h:72
bool lowPriorityPopups
Whether to show popups for low priority notifications.
Definition settings.h:47
QFlags< T > & setFlag(Enum flag, bool on)
bool testFlag(Enum flag) const const
void append(QList< T > &&value)
QObject(QObject *parent)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 25 2025 11:55:44 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.