diff --git a/modules/notification/notification_center.py b/modules/notification/notification_center.py index a45ec83..0e0e346 100644 --- a/modules/notification/notification_center.py +++ b/modules/notification/notification_center.py @@ -65,65 +65,6 @@ class ExpandableNotificationGroup(Box): notification_widget = NotificationCenterWidget( notification=latest_notification ) - # single_notification = Box( - # name="single-notification-content", - # spacing=8, - # children=[ - # Box( - # name="notification-image", - # children=CustomImage( - # pixbuf=self._get_notification_pixbuf( - # latest_notification._notification - # ) - # ), - # ), - # Box( - # name="notification-text", - # orientation="v", - # v_align="center", - # h_expand=True, - # children=[ - # Box( - # name="notification-summary-box", - # orientation="h", - # children=[ - # Label( - # name="notification-summary", - # markup=f"{self.app_name}", - # h_align="start", - # ellipsization="end", - # ), - # ], - # ), - # Label( - # name="notification-body", - # markup=latest_notification._notification.summary.replace( - # "\n", " " - # ), - # max_chars_width=35, - # h_align="start", - # ellipsization="end", - # ), - # ], - # ), - # Box( - # orientation="v", - # children=[ - # Button( - # name="notification-close", - # image=CustomImage( - # icon_name="close-symbolic", icon_size=18 - # ), - # visible=True, - # on_clicked=lambda *_: self._close_single_notification_and_stop_propagation( - # latest_notification - # ), - # ), - # Box(v_expand=True), - # ], - # ), - # ], - # ) self.collapsed_eventbox.add(notification_widget) else: # Multiple notifications - create stacked effect @@ -138,7 +79,6 @@ class ExpandableNotificationGroup(Box): if num_notifications >= 3: bottom_shadow = Box( name="stack-shadow-bottom", - # style="min-height: 16px; margin-left: 20px; margin-right: -4px; margin-bottom: -8px;", ) stack_container.add(bottom_shadow) @@ -146,7 +86,6 @@ class ExpandableNotificationGroup(Box): if num_notifications >= 2: middle_shadow = Box( name="stack-shadow-middle", - # style="min-height: 20px; margin-left: 10px; margin-right: -2px; margin-bottom: -12px;", ) stack_container.add(middle_shadow) @@ -158,8 +97,8 @@ class ExpandableNotificationGroup(Box): Box( name="notification-image", children=CustomImage( - pixbuf=self._get_notification_pixbuf( - latest_notification._notification + pixbuf=self._get_notification_pixbuf_for_group( + latest_notification ) ), ), @@ -223,6 +162,51 @@ class ExpandableNotificationGroup(Box): # Create expanded state (hidden initially) self.create_expanded_state() + def _get_notification_pixbuf_for_group(self, cached_notification): + """Get notification pixbuf using cached image key - fallback to app icon""" + notification = cached_notification._notification + notification_id = getattr(notification, "id", None) + + # First try to get cached notification image using stored cache key + if hasattr(cached_notification, 'cache_metadata') and cached_notification.cache_metadata: + notification_image_cache_key = cached_notification.cache_metadata.get('notification_image_cache_key') + if notification_image_cache_key: + try: + cached_image = get_cached_notification_image(notification_image_cache_key) + if cached_image: + logger.debug(f"Using cached notification image: {notification_image_cache_key}") + return cached_image + except Exception as e: + logger.debug(f"Failed to load cached notification image: {e}") + + # Fallback to app icon using cached key + if hasattr(cached_notification, 'cache_metadata') and cached_notification.cache_metadata: + app_icon_cache_key = cached_notification.cache_metadata.get('app_icon_cache_key') + if app_icon_cache_key: + try: + from modules.notification.unified_cache import get_from_cache + cached_app_icon = get_from_cache(app_icon_cache_key, (35, 35)) + if cached_app_icon: + logger.debug(f"Using cached app icon: {app_icon_cache_key}") + return cached_app_icon + except Exception as e: + logger.debug(f"Failed to load cached app icon: {e}") + + # Final fallback - try to cache app icon directly if available + try: + app_icon_source = getattr(notification, "app_icon", None) + if app_icon_source: + cached_app_icon = cache_notification_icon(app_icon_source, (35, 35)) + if cached_app_icon: + logger.debug(f"Using directly cached app icon for: {app_icon_source}") + return cached_app_icon + except Exception as e: + logger.debug(f"Failed to get directly cached app icon: {e}") + + # Ultimate fallback + logger.debug("Using fallback notification icon") + return get_fallback_notification_icon((35, 35)) + def create_expanded_state(self): # Create main expanded container self.expanded_container = Box( @@ -310,62 +294,6 @@ class ExpandableNotificationGroup(Box): # Hide the entire expanded container initially self.expanded_container.set_visible(False) - def _get_notification_pixbuf(self, notification): - """Simplified notification pixbuf for notification center""" - notification_id = getattr(notification, "id", None) - - try: - # Try to process current pixbuf if available - if hasattr(notification, "image_pixbuf") and notification.image_pixbuf: - try: - cache_key = get_notification_image_cache_key( - notification_id, notification.image_pixbuf - ) - cached_image = get_cached_notification_image(cache_key) - if cached_image: - logger.debug(f"Using cached notification image: {cache_key}") - return cached_image - - # Try to cache the image now - cache_path, cache_key = cache_notification_image( - notification_id, notification.image_pixbuf, (35, 35) - ) - - if cache_path and cache_key: - cached_pixbuf = get_cached_notification_image(cache_key) - if cached_pixbuf: - logger.debug( - f"Cached and using notification image: {cache_key}" - ) - return cached_pixbuf - - # Direct fallback if caching succeeded but loading failed - logger.debug("Using direct scaling for notification image") - return notification.image_pixbuf.scale_simple( - 35, 35, GdkPixbuf.InterpType.BILINEAR - ) - except Exception as image_error: - logger.debug(f"Notification image processing failed: {image_error}") - # Continue to app icon fallback - - except Exception as e: - logger.debug(f"Failed to process notification image: {e}") - - # Use cached app icon as fallback - try: - app_icon_source = getattr(notification, "app_icon", None) - if app_icon_source: - cached_app_icon = cache_notification_icon(app_icon_source, (35, 35)) - if cached_app_icon: - logger.debug(f"Using cached app icon for: {app_icon_source}") - return cached_app_icon - except Exception as e: - logger.debug(f"Failed to get cached app icon: {e}") - - # Ultimate fallback - logger.debug("Using fallback notification icon") - return get_fallback_notification_icon((35, 35)) - def on_clicked(self, widget, event): if event.button == 1: # Left click # Always allow expansion, even for single notifications @@ -505,6 +433,7 @@ class ExpandableNotificationGroup(Box): class NotificationCenterWidget(NotificationWidget): def __init__(self, notification, **kwargs): self.notification_id = notification.cache_id + self.cache_metadata = getattr(notification, 'cache_metadata', {}) super().__init__( notification._notification, @@ -514,6 +443,50 @@ class NotificationCenterWidget(NotificationWidget): **kwargs, ) + def _get_notification_pixbuf(self, notification): + """Get notification pixbuf using cached image key - fallback to app icon""" + notification_id = getattr(notification, "id", None) + + # First try to get cached notification image using stored cache key + if self.cache_metadata: + notification_image_cache_key = self.cache_metadata.get('notification_image_cache_key') + if notification_image_cache_key: + try: + cached_image = get_cached_notification_image(notification_image_cache_key) + if cached_image: + logger.debug(f"Using cached notification image: {notification_image_cache_key}") + return cached_image + except Exception as e: + logger.debug(f"Failed to load cached notification image: {e}") + + # Fallback to app icon using cached key + if self.cache_metadata: + app_icon_cache_key = self.cache_metadata.get('app_icon_cache_key') + if app_icon_cache_key: + try: + from modules.notification.unified_cache import get_from_cache + cached_app_icon = get_from_cache(app_icon_cache_key, (35, 35)) + if cached_app_icon: + logger.debug(f"Using cached app icon: {app_icon_cache_key}") + return cached_app_icon + except Exception as e: + logger.debug(f"Failed to load cached app icon: {e}") + + # Final fallback - try to cache app icon directly if available + try: + app_icon_source = getattr(notification, "app_icon", None) + if app_icon_source: + cached_app_icon = cache_notification_icon(app_icon_source, (35, 35)) + if cached_app_icon: + logger.debug(f"Using directly cached app icon for: {app_icon_source}") + return cached_app_icon + except Exception as e: + logger.debug(f"Failed to get directly cached app icon: {e}") + + # Ultimate fallback + logger.debug("Using fallback notification icon") + return get_fallback_notification_icon((35, 35)) + def create_content(self, notification): # Create our custom close button for notification center @@ -594,10 +567,8 @@ class NotificationCenterWidget(NotificationWidget): def _on_close_clicked(self, *args): try: - # Get notification cache metadata for cleanup - cache_metadata = getattr(self, "cache_metadata", {}) - if hasattr(self.notification, "_cache_metadata"): - cache_metadata = self.notification._cache_metadata + # Use instance cache metadata instead of notification attribute + cache_metadata = self.cache_metadata # Clean up caches using stored metadata from modules.notification.notification import ( @@ -629,7 +600,7 @@ class NotificationCenterWidget(NotificationWidget): # Override to disable auto-close functionality def close_notification(self): - pass + return False class NotificationCenter(Window): @@ -919,14 +890,5 @@ class NotificationCenter(Window): logger.debug(f"Notification center visibility set to: {visible}") def _on_destroy(self, *_): - try: - notification_service.disconnect( - "cached-notification-added", self.on_notification_added - ) - notification_service.disconnect( - "cached-notification-removed", self.on_notification_removed - ) - notification_service.disconnect("clear-all", self.on_clear_all) - notification_service.disconnect("notify::count", self.on_count_changed) - except Exception as e: - logger.error(f"Error disconnecting signals: {e}") + # Signals will be automatically disconnected when the object is destroyed + pass diff --git a/services/custom_notification.py b/services/custom_notification.py index 19aad33..fe3ddb1 100644 --- a/services/custom_notification.py +++ b/services/custom_notification.py @@ -92,19 +92,23 @@ class CachedNotification(Service): @Property(object, "readable") def image_pixbuf(self) -> GdkPixbuf.Pixbuf | None: - if self.image_pixmap: - return self.image_pixmap.as_pixbuf() - if self.image_file and os.path.exists(self.image_file): - try: - return GdkPixbuf.Pixbuf.new_from_file(self.image_file) - except Exception: - # If file can't be loaded, return None - pass + try: + if self.image_pixmap: + return self.image_pixmap.as_pixbuf() + if self.image_file and os.path.exists(self.image_file): + try: + return GdkPixbuf.Pixbuf.new_from_file(self.image_file) + except Exception: + # If file can't be loaded, return None + pass + except Exception: + # If any error occurs (including temp file gone), return None safely + pass return None @Property(dict, "readable") def serialized(self) -> dict: - """Enhanced serialization with cache metadata""" + """Enhanced serialization with cache metadata - stores only cache keys""" from modules.notification.notification import ( get_cache_key, get_notification_image_cache_key, @@ -117,17 +121,27 @@ class CachedNotification(Service): if self.app_icon: app_icon_cache_key = get_cache_key(self.app_icon, (35, 35), self.app_name) - if self.id and hasattr(self._notification, 'image_pixbuf'): - # Only try to access image_pixbuf if we can safely do so + # Only try to get notification image cache key if we can safely access image_pixbuf + if self.id: try: - # Check if image_pixbuf exists and can be accessed - image_pixbuf = getattr(self._notification, 'image_pixbuf', None) - if image_pixbuf: - notification_image_cache_key = get_notification_image_cache_key( - self.id, image_pixbuf - ) - except (AttributeError, OSError, Exception): - # If temp file is gone or any other error, just mark as None + # First check if we already have the cache key stored + if hasattr(self, 'cache_metadata') and self.cache_metadata: + notification_image_cache_key = self.cache_metadata.get('notification_image_cache_key') + + # If not, try to generate it safely + if not notification_image_cache_key and hasattr(self._notification, 'image_pixbuf'): + try: + # Check if image_pixbuf exists and can be accessed without loading from file + image_pixbuf = getattr(self._notification, 'image_pixbuf', None) + if image_pixbuf: + notification_image_cache_key = get_notification_image_cache_key( + self.id, image_pixbuf + ) + except (AttributeError, OSError, Exception): + # If temp file is gone or any other error, just mark as None + pass + except Exception: + # If any error occurs during cache key generation, skip it pass return { @@ -141,12 +155,11 @@ class CachedNotification(Service): "urgency": self.urgency, "actions": [(action.identifier, action.label) for action in self.actions], "image-file": self.image_file, - "image-pixmap": ( - self.image_pixmap.serialize() if self.image_pixmap else None - ), + # Only store image-pixmap if no cache key is available (fallback) + "image-pixmap": None, # Don't store image data, only cache key "timestamp": int(time.time()), "group": self.app_name, # Group notifications by app name - # Enhanced cache metadata + # Enhanced cache metadata - store only cache keys "cache_metadata": { "app_icon_cache_key": app_icon_cache_key, "notification_image_cache_key": notification_image_cache_key, @@ -284,6 +297,9 @@ class CachedNotifications(Notifications): """Handle notification added and cache it with enhanced metadata""" # Don't call super() - we're handling this ourselves + # Import logger at the top of the function + from loguru import logger + notification = self.get_notification_from_id(notification_id) if notification: @@ -300,8 +316,22 @@ class CachedNotifications(Notifications): # Check if this app should be ignored for history (don't cache) if notification.app_name in data.NOTIFICATION_IGNORED_APPS_HISTORY: # Don't cache notifications from ignored apps, but still allow popup display + logger.debug(f"Ignoring notification from {notification.app_name} (in ignore list)") return + # Check if we already have this notification cached (by notification.id) + existing_notification = None + for cached_notif in self._cached_notifications.values(): + if cached_notif._notification.id == notification.id: + existing_notification = cached_notif + break + + if existing_notification: + logger.debug(f"Notification ID {notification.id} already cached, skipping") + return + + logger.debug(f"Caching new notification: ID={notification.id}, App={notification.app_name}, Summary={notification.summary[:50]}...") + # Cache notifications (except for ignored apps) # DND only affects popup display, not caching cache_id = self._next_cache_id @@ -357,6 +387,10 @@ class CachedNotifications(Notifications): self.notify("count") self.emit("cached-notification-added", cached_notification) + + logger.debug(f"Successfully cached notification: Cache ID={cache_id}, Total cached={len(self._cached_notifications)}") + else: + print(f"WARNING: Failed to get notification with ID {notification_id}") def remove_cached_notification(self, notification_id: int): """Remove the notification of given id with enhanced cache cleanup"""