From eafb176d6e7a4b3f788b482c1777b5eea7a8d76e Mon Sep 17 00:00:00 2001 From: Zacharias-Brohn Date: Wed, 25 Feb 2026 17:08:04 +0100 Subject: [PATCH] notification cooldown --- Config/Config.qml | 19 +++++++++++++------ Config/NotifConfig.qml | 1 + Daemons/NotifServer.qml | 27 ++++++++++++++++++++++++++- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/Config/Config.qml b/Config/Config.qml index 716a02c..124ae6f 100644 --- a/Config/Config.qml +++ b/Config/Config.qml @@ -205,6 +205,7 @@ Singleton { return { expire: notifs.expire, defaultExpireTimeout: notifs.defaultExpireTimeout, + appNotifCooldown: notifs.appNotifCooldown, clearThreshold: notifs.clearThreshold, expandThreshold: notifs.expandThreshold, actionOnClick: notifs.actionOnClick, @@ -306,7 +307,8 @@ Singleton { fileView.setText(JSON.stringify(config, null, 4)); } catch (e) { - Toaster.toast(qsTr("Failed to serialize config"), e.message, "settings_alert", Toast.Error); + Toaster.toast(qsTr("Failed to serialize config"), e.message, + "settings_alert", Toast.Error); } } } @@ -337,7 +339,8 @@ Singleton { } onLoadFailed: err => { if (err !== FileViewError.FileNotFound) - Toaster.toast(qsTr("Failed to read config"), FileViewError.toString(err), "settings_alert", Toast.Warning); + Toaster.toast(qsTr("Failed to read config"), FileViewError.toString(err), + "settings_alert", Toast.Warning); } onLoaded: { ModeScheduler.checkStartup(); @@ -346,15 +349,19 @@ Singleton { const elapsed = timer.elapsedMs(); if (adapter.utilities.toasts.configLoaded && !root.recentlySaved) { - Toaster.toast(qsTr("Config loaded"), qsTr("Config loaded in %1ms").arg(elapsed), "rule_settings"); + Toaster.toast(qsTr("Config loaded"), qsTr("Config loaded in %1ms").arg( + elapsed), "rule_settings"); } else if (adapter.utilities.toasts.configLoaded && root.recentlySaved) { - Toaster.toast(qsTr("Config saved"), qsTr("Config reloaded in %1ms").arg(elapsed), "settings_alert"); + Toaster.toast(qsTr("Config saved"), qsTr("Config reloaded in %1ms").arg( + elapsed), "settings_alert"); } } catch (e) { - Toaster.toast(qsTr("Failed to load config"), e.message, "settings_alert", Toast.Error); + Toaster.toast(qsTr("Failed to load config"), e.message, "settings_alert", + Toast.Error); } } - onSaveFailed: err => Toaster.toast(qsTr("Failed to save config"), FileViewError.toString(err), "settings_alert", Toast.Error) + onSaveFailed: err => Toaster.toast(qsTr("Failed to save config"), + FileViewError.toString(err), "settings_alert", Toast.Error) JsonAdapter { id: adapter diff --git a/Config/NotifConfig.qml b/Config/NotifConfig.qml index fde337b..0a11d46 100644 --- a/Config/NotifConfig.qml +++ b/Config/NotifConfig.qml @@ -2,6 +2,7 @@ import Quickshell.Io JsonObject { property bool actionOnClick: false + property int appNotifCooldown: 0 property real clearThreshold: 0.3 property int defaultExpireTimeout: 5000 property int expandThreshold: 20 diff --git a/Daemons/NotifServer.qml b/Daemons/NotifServer.qml index 8937e57..c4cec7a 100644 --- a/Daemons/NotifServer.qml +++ b/Daemons/NotifServer.qml @@ -23,6 +23,29 @@ Singleton { readonly property list popups: list.filter(n => n.popup) property alias server: server + readonly property var appCooldownMap: new Map() + + function shouldThrottle(appName: string): bool { + if ( props.dnd ) + return false; + + const key = ( appName || "unknown" ).trim().toLowerCase(); + const cooldownSec = Config.notifs.appNotifCooldown; + const cooldownMs = Math.max(0, cooldownSec * 1000); + + if ( cooldownMs <= 0 ) + return true; + + const now = Date.now(); + const until = appCooldownMap.get( key ) ?? 0; + + if ( now < until ) + return false; + + appCooldownMap.set( key, now + cooldownMs ) + return true; + } + onListChanged: { if (loaded) { saveTimer.restart(); @@ -77,8 +100,10 @@ Singleton { onNotification: notif => { notif.tracked = true; + const is_popup = root.shouldThrottle(notif.appName); + const comp = notifComp.createObject(root, { - popup: !props.dnd, + popup: is_popup, notification: notif }); root.list = [comp, ...root.list];