fix: add destroy() and signal cleanup across widgets, fix settings window + menu regressions

P0 leak fixes: context/action menus destroyed on deactivate, config
callbacks unregistered in settings widgets, recorder and server signal
IDs stored and disconnected, bluetooth client closed and unsubscribed,
rfkill timer removed, seek timers cancelled, dropdown and options menus
freed, app name cache LRU-bounded.

Fix desktop 'Edit Widgets' and settings menu regressions by deferring
destroy out of Gtk signal handlers via GLib.idle_add.

Fix settings entry fields by connecting the activate signal explicitly
(Enter saves the value).
This commit is contained in:
sankalp committed 2026-08-01 19:44:39 +05:45
1 parent 55fc3fba21
commit d8696b9198
13 files changed
+180 -44

No files matched your search

+40 -6
View File
@@ -1,4 +1,4 @@
from fabric.utils import Gdk, Gtk
from fabric.utils import Gdk, GLib, Gtk
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.centerbox import CenterBox
@@ -7,7 +7,7 @@ from fabric.widgets.label import Label
from fabric.widgets.scrolledwindow import ScrolledWindow
from fabric.widgets.stack import Stack
from services.config import config, on_config_change
from services.config import config, off_config_change, on_config_change
from shared.widgets.smooth_switch import SmoothSwitch
from utils.gtk_utils import setup_cursor_hover, svg_file
@@ -66,6 +66,10 @@ class SettingsSwitch(SmoothSwitch):
config().set(self.config_key, state)
config().save()
def destroy(self):
off_config_change(self._on_config_change)
super().destroy()
class SettingsEntry(Entry):
def __init__(self, config_key: str, **kwargs):
@@ -75,9 +79,9 @@ class SettingsEntry(Entry):
super().__init__(
name="settings-entry",
text=initial_value,
on_activate=self.on_change,
**kwargs,
)
self.connect("activate", self.on_change)
# Sync with external config changes
on_config_change(self._on_config_change)
@@ -101,6 +105,10 @@ class SettingsEntry(Entry):
config().set(self.config_key, value)
config().save()
def destroy(self):
off_config_change(self._on_config_change)
super().destroy()
class SettingsComboBox(Gtk.ComboBoxText):
def __init__(self, config_key: str, options: list, **kwargs):
@@ -134,6 +142,10 @@ class SettingsComboBox(Gtk.ComboBoxText):
config().set(self.config_key, value)
config().save()
def destroy(self):
off_config_change(self._on_config_change)
super().destroy()
class SettingsList(Box):
"""A list of tags that can be added or removed"""
@@ -146,8 +158,8 @@ class SettingsList(Box):
self.entry = Entry(
name="settings-list-entry",
placeholder="Add new item...",
on_activate=self.on_add,
)
self.entry.connect("activate", self.on_add)
self.add(self.list_box)
self.add(self.entry)
@@ -206,6 +218,10 @@ class SettingsList(Box):
config().save()
self._update_list()
def destroy(self):
off_config_change(self._on_config_change)
super().destroy()
class SettingsPage(ScrolledWindow):
def __init__(self, title: str, rows: list, **kwargs):
@@ -246,8 +262,11 @@ class SettingsWindow(Gtk.Window):
self.set_position(Gtk.WindowPosition.CENTER)
self.set_visible(False)
# Hide instead of destroy so the __main__ reference stays valid
self.connect("delete-event", lambda *_: self.hide() or True)
# Destroy on close so the widget tree is freed; get_settings_window
# recreates it on next open. Destruction is deferred out of the
# delete-event emission to avoid tearing the window down mid-signal.
self.connect("delete-event", self._on_delete_event)
self.connect("destroy", self._on_window_destroyed)
self.stack = Stack(
name="settings-stack",
@@ -508,6 +527,11 @@ class SettingsWindow(Gtk.Window):
)
def toggle(self):
if getattr(self, "_destroyed", False):
# A stale reference (e.g. __main__.settings) outlived this window.
get_settings_window().toggle()
return
if not self.get_visible():
self.show_all()
self.present()
@@ -522,6 +546,16 @@ class SettingsWindow(Gtk.Window):
else:
self.hide()
def _on_delete_event(self, *_):
GLib.idle_add(self.destroy)
return True
def _on_window_destroyed(self, *_):
global _settings_window
self._destroyed = True
if _settings_window is self:
_settings_window = None
_settings_window = None