diff --git a/.gitignore b/.gitignore
index d6360f0..e68a251 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,5 +11,3 @@ config/assets/screentime.json
config/hypr/modus.conf
config/hypr/colors.conf
styles/colors.css
-gray/*
-gray
diff --git a/config/data.py b/config/data.py
index fad426f..9267ac4 100644
--- a/config/data.py
+++ b/config/data.py
@@ -41,12 +41,11 @@ DOCK_THEME = "Pills"
def load_config():
"""Load the configuration from config.json"""
- config_path = os.path.expanduser(f"~/{APP_NAME_CAP}/config/assets/config.json")
config = {}
- if os.path.exists(config_path):
+ if os.path.exists(CONFIG_FILE):
try:
- with open(config_path, "r") as f:
+ with open(CONFIG_FILE, "r") as f:
config = json.load(f)
except Exception as e:
print(f"Error loading config: {e}")
diff --git a/config/settings/constants.py b/config/settings/constants.py
index b5325bc..447b621 100644
--- a/config/settings/constants.py
+++ b/config/settings/constants.py
@@ -58,7 +58,7 @@ DEFAULTS = {
"dock_notifications_visible": True,
"dock_applications_visible": True,
"dock_language_visible": True,
- 'dock_hide_special_workspace': True,
+ "dock_hide_special_workspace": True,
"workspace_nums": False,
"workspace_use_chinese_numerals": False,
"workspace_dots": False,
@@ -71,4 +71,6 @@ DEFAULTS = {
"gpu": False,
},
"metrics_disks": ["/"],
+ "limited_apps_history": ["Spotify"],
+ "history_ignored_apps": ["Modus"],
}
diff --git a/config/settings/gui.py b/config/settings/gui.py
index e8f3133..ae2483f 100644
--- a/config/settings/gui.py
+++ b/config/settings/gui.py
@@ -662,6 +662,63 @@ class HyprConfGUI(Window):
)
system_grid.attach(note_label, 2, row, 2, 1)
+ # Notifications app lists section
+ notifications_header = Label(
+ markup="Notification Settings", h_align="start"
+ )
+ vbox.add(notifications_header)
+
+ notif_grid = Gtk.Grid()
+ notif_grid.set_column_spacing(20)
+ notif_grid.set_row_spacing(10)
+ notif_grid.set_margin_start(10)
+ notif_grid.set_margin_top(5)
+ notif_grid.set_margin_bottom(15)
+ vbox.add(notif_grid)
+
+ # Limited Apps History
+ limited_apps_label = Label(
+ label="Limited Apps History:", h_align="start", v_align="center"
+ )
+ notif_grid.attach(limited_apps_label, 0, 0, 1, 1)
+
+ limited_apps_list = bind_vars.get("limited_apps_history", ["Spotify"])
+ limited_apps_text = ", ".join(f'"{app}"' for app in limited_apps_list)
+ self.limited_apps_entry = Entry(
+ text=limited_apps_text,
+ tooltip_text='Enter app names separated by commas, e.g: "Spotify", "Discord"',
+ h_expand=True,
+ )
+ notif_grid.attach(self.limited_apps_entry, 1, 0, 1, 1)
+
+ limited_apps_hint = Label(
+ markup='Apps with limited notification history (format: "App1", "App2")',
+ h_align="start",
+ )
+ notif_grid.attach(limited_apps_hint, 0, 1, 2, 1)
+
+ # History Ignored Apps
+ ignored_apps_label = Label(
+ label="History Ignored Apps:", h_align="start", v_align="center"
+ )
+ notif_grid.attach(ignored_apps_label, 0, 2, 1, 1)
+
+ ignored_apps_list = bind_vars.get("history_ignored_apps", ["Modus"])
+ ignored_apps_text = ", ".join(f'"{app}"' for app in ignored_apps_list)
+ self.ignored_apps_entry = Entry(
+ text=ignored_apps_text,
+ tooltip_text='Enter app names separated by commas, e.g: "Modus", "Screenshot"',
+ h_expand=True,
+ )
+ notif_grid.attach(self.ignored_apps_entry, 1, 2, 1, 1)
+
+ ignored_apps_hint = Label(
+ markup='Apps whose notifications are ignored in history (format: "App1", "App2")',
+ h_align="start",
+ )
+ notif_grid.attach(ignored_apps_hint, 0, 3, 2, 1)
+
+
metrics_header = Label(markup="System Metrics Options", h_align="start")
vbox.add(metrics_header)
metrics_grid = Gtk.Grid(
@@ -771,18 +828,6 @@ class HyprConfGUI(Window):
repo_box.add(repo_link)
vbox.add(repo_box)
- # def on_kofi_clicked(_):
- # import webbrowser
-
- # webbrowser.open("https://github.com/S4NKALP")
-
- # kofi_btn = Button(
- # label="Support on Ko-Fi ❤️",
- # on_clicked=on_kofi_clicked,
- # tooltip_text="Support S4NKALP on Ko-Fi",
- # style="margin-top: 18px; min-width: 160px;",
- # )
- # vbox.add(kofi_btn)
vbox.add(Box(v_expand=True))
return vbox
@@ -919,6 +964,29 @@ class HyprConfGUI(Window):
and isinstance(child.get_children()[0], Entry)
]
+ # Parse notification app lists
+ def parse_app_list(text):
+ """Parse comma-separated app names with quotes"""
+ if not text.strip():
+ return []
+ apps = []
+ for app in text.split(","):
+ app = app.strip()
+ if app.startswith('"') and app.endswith('"'):
+ app = app[1:-1]
+ elif app.startswith("'") and app.endswith("'"):
+ app = app[1:-1]
+ if app:
+ apps.append(app)
+ return apps
+
+ current_bind_vars_snapshot["limited_apps_history"] = parse_app_list(
+ self.limited_apps_entry.get_text()
+ )
+ current_bind_vars_snapshot["history_ignored_apps"] = parse_app_list(
+ self.ignored_apps_entry.get_text()
+ )
+
selected_icon_path = self.selected_face_icon
replace_lock = self.lock_switch and self.lock_switch.get_active()
replace_idle = self.idle_switch and self.idle_switch.get_active()
@@ -1188,6 +1256,15 @@ class HyprConfGUI(Window):
for p in DEFAULTS.get("metrics_disks", ["/"]):
self._add_disk_edit_entry_func(p)
+ # Reset notification app lists
+ limited_apps_list = DEFAULTS.get("limited_apps_history", ["Spotify"])
+ limited_apps_text = ", ".join(f'"{app}"' for app in limited_apps_list)
+ self.limited_apps_entry.set_text(limited_apps_text)
+
+ ignored_apps_list = DEFAULTS.get("history_ignored_apps", ["Hyprshot"])
+ ignored_apps_text = ", ".join(f'"{app}"' for app in ignored_apps_list)
+ self.ignored_apps_entry.set_text(ignored_apps_text)
+
self._update_panel_position_sensitivity()
self.selected_face_icon = None
diff --git a/modules/dock/components/notifications.py b/modules/dock/components/notifications.py
index e5756d8..1f2fc94 100644
--- a/modules/dock/components/notifications.py
+++ b/modules/dock/components/notifications.py
@@ -11,7 +11,7 @@ from gi.repository import Gdk, GLib, Gtk
import config.data as data
import utils.icons as icons
-from modules.notification_popup import NotificationBox
+from modules.notification_popup import NotificationBox, get_limited_apps_history, get_history_ignored_apps
from utils.custom_image import CustomImage
from utils.notification_utils import (
CONFIG_FILE,
@@ -169,8 +169,6 @@ class NotificationHistory(Box):
self._load_and_sync_dnd_state()
- self.LIMITED_APPS_HISTORY = ["Spotify"]
-
def _connect_to_shared_history(self):
try:
shared_history = get_shared_notification_history()
diff --git a/modules/notification_popup.py b/modules/notification_popup.py
index 815640b..054d10b 100644
--- a/modules/notification_popup.py
+++ b/modules/notification_popup.py
@@ -15,15 +15,24 @@ import utils.icons as icons
from utils.custom_image import CustomImage
from utils.wayland import WaylandWindow as Window
from utils.notification_utils import (
- LIMITED_APPS,
cache_notification_pixbuf,
load_scaled_pixbuf,
get_shared_notification_history,
NotificationHistory,
- delete_cached_image
+ delete_cached_image,
)
+def get_limited_apps_history():
+ config = data.load_config()
+ return config.get("limited_apps_history", ["Spotify"])
+
+
+def get_history_ignored_apps():
+ config = data.load_config()
+ return config.get("history_ignored_apps", ["Modus"])
+
+
class ActionButton(Button):
def __init__(
self, action: NotificationAction, index: int, total: int, notification_box
@@ -115,18 +124,18 @@ class NotificationBox(Box):
def set_is_history(self, is_history):
# Ensure the object is properly initialized
- if not hasattr(self, '_is_history'):
+ if not hasattr(self, "_is_history"):
self._is_history = False
self._is_history = is_history
def set_container(self, container):
# Ensure the object is properly initialized
- if not hasattr(self, '_container'):
+ if not hasattr(self, "_container"):
self._container = None
self._container = container
def get_container(self):
- return getattr(self, '_container', None)
+ return getattr(self, "_container", None)
def create_header(self):
notification = self.notification
@@ -268,12 +277,12 @@ class NotificationBox(Box):
return self.close_button
def on_hover_enter(self, *args):
- container = getattr(self, '_container', None)
+ container = getattr(self, "_container", None)
if container:
container.pause_and_reset_all_timeouts()
def on_hover_leave(self, *args):
- container = getattr(self, '_container', None)
+ container = getattr(self, "_container", None)
if container:
container.resume_all_timeouts()
@@ -282,14 +291,18 @@ class NotificationBox(Box):
self._timeout_id = GLib.timeout_add(self.timeout_ms, self.close_notification)
def stop_timeout(self):
- if hasattr(self, '_timeout_id') and self._timeout_id is not None:
+ if hasattr(self, "_timeout_id") and self._timeout_id is not None:
GLib.source_remove(self._timeout_id)
self._timeout_id = None
def close_notification(self):
# Safety check: ensure _destroyed attribute exists
- if not hasattr(self, '_destroyed'):
- logger.warning(f"NotificationBox {getattr(self, 'uuid', 'unknown')} missing _destroyed attribute, skipping close")
+ if not hasattr(self, "_destroyed"):
+ logger.warning(
+ f"NotificationBox {
+ getattr(self, 'uuid', 'unknown')
+ } missing _destroyed attribute, skipping close"
+ )
return False
if not self._destroyed:
@@ -313,12 +326,12 @@ class NotificationBox(Box):
if (
hasattr(self, "cached_image_path")
and self.cached_image_path
- and (not getattr(self, '_is_history', False) or from_history_delete)
+ and (not getattr(self, "_is_history", False) or from_history_delete)
):
delete_cached_image(self.cached_image_path)
# Ensure _destroyed attribute exists before setting it
- if hasattr(self, '_destroyed'):
+ if hasattr(self, "_destroyed"):
self._destroyed = True
else:
# If _destroyed doesn't exist, create it
@@ -328,18 +341,17 @@ class NotificationBox(Box):
super().destroy()
def hover_button(self, button):
- container = getattr(self, '_container', None)
+ container = getattr(self, "_container", None)
if container:
container.pause_and_reset_all_timeouts()
def unhover_button(self, button):
- container = getattr(self, '_container', None)
+ container = getattr(self, "_container", None)
if container:
container.resume_all_timeouts()
class NotificationContainer(Box):
-
def __init__(
self,
notification_history_instance: NotificationHistory,
@@ -439,7 +451,25 @@ class NotificationContainer(Box):
notification.connect("closed", self.on_notification_closed)
app_name = notification.app_name
- if app_name in LIMITED_APPS:
+ if app_name in get_history_ignored_apps():
+ logger.info(
+ f"Ignoring notification from {app_name} as it is in the ignored list."
+ )
+ # Don't add to history, just show the notification and let it timeout normally
+ new_box.set_is_history(False)
+ # Continue with normal notification display but skip history logic
+ self.stack.add_named(new_box, str(id))
+ self.notifications.append(new_box)
+ self.current_index = len(self.notifications) - 1
+ self.stack.set_visible_child(new_box)
+
+ for notification_box in self.notifications:
+ notification_box.start_timeout()
+ self.main_revealer.show_all()
+ self.main_revealer.set_reveal_child(True)
+ self.update_navigation_buttons()
+ return
+ if app_name in get_limited_apps_history():
notification_history_instance.clear_history_for_app(app_name)
existing_notification_index = -1
@@ -531,33 +561,41 @@ class NotificationContainer(Box):
notification_history_instance = self.notification_history
- notif_box.set_is_history(True)
- notification_history_instance.add_notification(notif_box)
- notif_box.stop_timeout()
+ # Check if this notification should be ignored from history
+ app_name = notification.app_name
+ if app_name not in get_history_ignored_apps():
+ notif_box.set_is_history(True)
+ notification_history_instance.add_notification(notif_box)
- if reason_str == "NotificationCloseReason.DISMISSED_BY_USER":
- logger.info(
- f"User dismissed notification {
- notification.id
- }, but still added to history"
- )
- elif (
- reason_str == "NotificationCloseReason.EXPIRED"
- or reason_str == "NotificationCloseReason.CLOSED"
- or reason_str == "NotificationCloseReason.UNDEFINED"
- ):
- logger.info(
- f"Notification {
- notification.id
- } closed automatically, added to history"
- )
+ if reason_str == "NotificationCloseReason.DISMISSED_BY_USER":
+ logger.info(
+ f"User dismissed notification {
+ notification.id
+ }, added to history"
+ )
+ elif (
+ reason_str == "NotificationCloseReason.EXPIRED"
+ or reason_str == "NotificationCloseReason.CLOSED"
+ or reason_str == "NotificationCloseReason.UNDEFINED"
+ ):
+ logger.info(
+ f"Notification {
+ notification.id
+ } closed automatically, added to history"
+ )
+ else:
+ logger.warning(
+ f"Unknown close reason: {reason_str} for notification {
+ notification.id
+ }. Still added to history."
+ )
else:
- logger.warning(
- f"Unknown close reason: {reason_str} for notification {
- notification.id
- }. Still added to history."
+ logger.info(
+ f"Notification from {app_name} closed but not added to history (ignored app)"
)
+ notif_box.stop_timeout()
+
if len(self.notifications) == 1:
self._is_destroying = True
self.main_revealer.set_reveal_child(False)
diff --git a/utils/notification_utils.py b/utils/notification_utils.py
index b15aec0..32c6fd8 100644
--- a/utils/notification_utils.py
+++ b/utils/notification_utils.py
@@ -18,7 +18,6 @@ NOTIFICATION_IMAGE_PREFIX = "notification_"
NOTIFICATION_IMAGE_SUFFIX = ".png"
DEFAULT_NOTIFICATION_IMAGE_SIZE = 48
MAX_PERSISTENT_NOTIFICATIONS = 100
-LIMITED_APPS = ["Spotify"]
# Global shared notification history instance
_shared_notification_history = None