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