diff --git a/src/controlCenter/controlCenter.vala b/src/controlCenter/controlCenter.vala index 7c2480e0..1b920d48 100644 --- a/src/controlCenter/controlCenter.vala +++ b/src/controlCenter/controlCenter.vala @@ -376,10 +376,12 @@ namespace SwayNotificationCenter { if (this.visible == visibility) { return; } - // Destroy the wl_surface to get a new "enter-monitor" signal and - // fixes issues where keyboard shortcuts stop working after clearing - // all notifications. - ((Gtk.Widget) this).unrealize (); + + // NOTE: We removed the unrealize() call that was here. + // It was causing GPU memory leaks on repeated open/close cycles + // because textures had to be re-uploaded each time. + // If keyboard shortcuts stop working after clearing notifications, + // a different fix may be needed (e.g., reset keyboard mode via layer shell). this.set_visible (visibility); diff --git a/src/controlCenter/widgets/mpris/mpris_player.vala b/src/controlCenter/widgets/mpris/mpris_player.vala index 0aa47dcc..9e27f357 100644 --- a/src/controlCenter/widgets/mpris/mpris_player.vala +++ b/src/controlCenter/widgets/mpris/mpris_player.vala @@ -98,8 +98,20 @@ namespace SwayNotificationCenter.Widgets.Mpris { album_art.set_visible (mpris_config.show_album_art == AlbumArtState.ALWAYS); } + public override void dispose () { + before_destroy (); + base.dispose (); + } + public void before_destroy () { source.properties_changed.disconnect (properties_changed); + + // Cancel any ongoing album art download + album_art_cancellable.cancel (); + + // Clear album art textures to release VRAM/GPU memory + album_art.clear (); + background_picture.set_paintable (null); } private void properties_changed (string iface, @@ -264,6 +276,10 @@ namespace SwayNotificationCenter.Widgets.Mpris { album_art_cancellable.cancel (); album_art_cancellable.reset (); + // Clear previous textures before loading new ones to release GPU memory + album_art.clear (); + background_picture.set_paintable (null); + Gdk.Texture ?album_art_texture = null; try { Uri uri = Uri.parse (art_data, UriFlags.NONE); diff --git a/src/notification/notification.vala b/src/notification/notification.vala index 3d2bfa3e..65ad276e 100644 --- a/src/notification/notification.vala +++ b/src/notification/notification.vala @@ -105,6 +105,22 @@ namespace SwayNotificationCenter { private Notification () {} + public override void dispose () { + // Clear all image resources to release VRAM/GPU memory + img.clear (); + img_app_icon.clear (); + body_image.set_paintable (null); + + // Remove any pending timeout + remove_noti_timeout (); + + // Clear gesture/controller references + gesture = null; + motion_controller = null; + + base.dispose (); + } + /** Show a non-timed notification */ public Notification.regular (NotifyParams param, NotificationType notification_type) { @@ -309,7 +325,8 @@ namespace SwayNotificationCenter { this.body.set_lines (this.number_of_body_lines); - // Reset state + // Reset state - clear paintable to release GPU memory + body_image.set_paintable (null); body_image.hide (); // Removes all image tags and adds them to an array @@ -344,8 +361,23 @@ namespace SwayNotificationCenter { string img = Functions.uri_to_path (img_paths[0]); File file = File.new_for_path (img); if (img.length > 0 && file.query_exists ()) { - Gdk.Texture texture = Gdk.Texture.from_file (file); - body_image.set_paintable (texture); + // Clear previous body image to release GPU memory + body_image.set_paintable (null); + // Load image scaled to display size to save GPU memory + // Full-res images (e.g., 2560x1440 screenshots) waste VRAM + try { + Gdk.Pixbuf pixbuf = new Gdk.Pixbuf.from_file_at_scale ( + img, + notification_body_image_width * get_scale_factor (), + notification_body_image_height * get_scale_factor (), + true); // preserve aspect ratio + Gdk.Texture texture = Gdk.Texture.for_pixbuf (pixbuf); + body_image.set_paintable (texture); + } catch (Error e) { + // Fallback to full-size load if scaling fails + Gdk.Texture texture = Gdk.Texture.from_file (file); + body_image.set_paintable (texture); + } body_image.set_can_shrink (true); body_image.set_content_fit (Gtk.ContentFit.SCALE_DOWN); body_image.width_request = notification_body_image_width; @@ -485,7 +517,8 @@ namespace SwayNotificationCenter { } private void set_style_urgency () { - // Reset state + // Reset state - clear paintable to release GPU memory + body_image.set_paintable (null); base_box.remove_css_class ("low"); base_box.remove_css_class ("normal"); base_box.remove_css_class ("critical"); @@ -505,7 +538,8 @@ namespace SwayNotificationCenter { } private void set_inline_reply () { - // Reset state + // Reset state - clear paintable to release GPU memory + body_image.set_paintable (null); inline_reply_box.hide (); // Only show inline replies in popup notifications if the compositor // supports ON_DEMAND layer shell keyboard interactivity diff --git a/src/notificationGroup/notificationGroup.vala b/src/notificationGroup/notificationGroup.vala index b0f7efa7..03f11651 100644 --- a/src/notificationGroup/notificationGroup.vala +++ b/src/notificationGroup/notificationGroup.vala @@ -207,6 +207,24 @@ namespace SwayNotificationCenter { }); } + public override void dispose () { + // Stop and clean up animation to prevent reference cycles + if (remove_animation != null) { + remove_animation.skip (); + if (remove_animation_done_id > 0) { + remove_animation.disconnect (remove_animation_done_id); + remove_animation_done_id = 0; + } + remove_animation = null; + } + + // Clear collections + urgent_notifications.clear (); + notification_ids.clear (); + + base.dispose (); + } + private void animation_value_changed (double progress) { queue_resize (); } diff --git a/src/underlay/underlay.vala b/src/underlay/underlay.vala index 5154cf24..0aa8c5ce 100644 --- a/src/underlay/underlay.vala +++ b/src/underlay/underlay.vala @@ -33,6 +33,21 @@ public class Underlay : Gtk.Widget { } } + public override void dispose () { + // Properly unparent children to release resources + if (_underlay_child != null) { + _underlay_child.unparent (); + _underlay_child = null; + } + + if (_child != null) { + _child.unparent (); + _child = null; + } + + base.dispose (); + } + /* * Overrides */