hope it fix memory leak
This commit is contained in:
6 files changed
+245
-126
No files matched your search
@@ -3,6 +3,7 @@
|
||||
font-family: "SF Pro Rounded";
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#control-center-menu {
|
||||
background-color: transparent;
|
||||
border-radius: 12px;
|
||||
@@ -32,6 +33,7 @@
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#control-center-widgets {
|
||||
background-color: alpha(#010101, 0.01);
|
||||
box-shadow: inset 0 0 0 1px alpha(#aaa, 0.4);
|
||||
@@ -39,8 +41,9 @@
|
||||
border-radius: 12px;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
#focus-widget {
|
||||
}
|
||||
|
||||
#focus-widget {}
|
||||
|
||||
#wb-widget,
|
||||
#brightness-menu {
|
||||
background-color: alpha(#000, 0.08);
|
||||
@@ -48,6 +51,7 @@
|
||||
border: 1px solid alpha(#111, 0.4);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
/* Widgets */
|
||||
#wifi-widget,
|
||||
#bluetooth-widget,
|
||||
@@ -301,9 +305,11 @@
|
||||
margin-top: -10px;
|
||||
/* border: 1px solid alpha(#fff, 1); */
|
||||
}
|
||||
|
||||
#volume-widget-slider {
|
||||
/* margin-bottom: 2px; */
|
||||
}
|
||||
|
||||
#volume-widget-icon {
|
||||
font-size: 22px;
|
||||
margin-top: -35px;
|
||||
@@ -344,6 +350,7 @@
|
||||
/* min-height: 40px; */
|
||||
/* margin: -9px; */
|
||||
}
|
||||
|
||||
#control-center-menu scale {
|
||||
background-color: transparent;
|
||||
margin-top: 10px;
|
||||
@@ -377,7 +384,7 @@
|
||||
/* Per-app volume control styles */
|
||||
#per-app-volume-control {
|
||||
min-height: 140px;
|
||||
min-width: 350px;
|
||||
min-width: 328px;
|
||||
/* background-color: alpha(#000, 0.3); */
|
||||
/* background-color: alpha(#999, 0.1); */
|
||||
/* box-shadow: inset 0 0 200px 0 alpha(#111, 0.3); */
|
||||
@@ -392,6 +399,7 @@
|
||||
min-height: 120px;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
#back-button {
|
||||
padding: 8px 12px;
|
||||
background-color: alpha(#fff, 0.1);
|
||||
@@ -402,6 +410,7 @@
|
||||
font-weight: 500;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
#back-button:hover {
|
||||
background-color: alpha(#fff, 0.18);
|
||||
}
|
||||
@@ -617,8 +626,10 @@
|
||||
|
||||
/* macOS-style expanded player - more compact */
|
||||
#macos-outer-player-box {
|
||||
min-height: 80px; /* Reduced height */
|
||||
min-width: 380px; /* Slightly wider to accommodate expanded track info */
|
||||
min-height: 80px;
|
||||
/* Reduced height */
|
||||
min-width: 380px;
|
||||
/* Slightly wider to accommodate expanded track info */
|
||||
/* margin: 10px; */
|
||||
padding: 10px;
|
||||
/* background-color: alpha(#000, 0.4); */
|
||||
@@ -633,6 +644,7 @@
|
||||
min-height: 70px;
|
||||
border-radius: 9px;
|
||||
}
|
||||
|
||||
#macos-album-image {
|
||||
min-width: 70px;
|
||||
min-height: 70px;
|
||||
@@ -651,6 +663,7 @@
|
||||
background-size: cover;
|
||||
box-shadow: 0 2px 8px alpha(#000, 0.3);
|
||||
}
|
||||
|
||||
#macos-album-image image {
|
||||
min-width: 70px;
|
||||
min-height: 70px;
|
||||
@@ -686,6 +699,7 @@
|
||||
color: alpha(#999, 0.8);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* macOS seek bar styling */
|
||||
#macos-seek-bar {
|
||||
background-color: transparent;
|
||||
@@ -735,17 +749,20 @@
|
||||
|
||||
/* macOS control buttons - more compact */
|
||||
#macos-button-box {
|
||||
margin-top: -1px; /* Reduced top margin */
|
||||
margin-top: -1px;
|
||||
/* Reduced top margin */
|
||||
}
|
||||
|
||||
#macos-control-button {
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
min-width: 28px; /* Slightly smaller */
|
||||
min-width: 28px;
|
||||
/* Slightly smaller */
|
||||
opacity: 0.7;
|
||||
min-height: 28px;
|
||||
padding: 5px; /* Reduced padding */
|
||||
padding: 5px;
|
||||
/* Reduced padding */
|
||||
color: #ffffff;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
@@ -758,10 +775,12 @@
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
min-width: 36px; /* Slightly smaller */
|
||||
min-width: 36px;
|
||||
/* Slightly smaller */
|
||||
opacity: 0.7;
|
||||
min-height: 36px;
|
||||
padding: 7px; /* Reduced padding */
|
||||
padding: 7px;
|
||||
/* Reduced padding */
|
||||
color: #ffffff;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
@@ -772,16 +791,19 @@
|
||||
|
||||
/* macOS player switcher dots - compact horizontal layout */
|
||||
#macos-stack-buttons-box {
|
||||
margin: 2px 5px; /* Minimal margins */
|
||||
margin: 2px 5px;
|
||||
/* Minimal margins */
|
||||
}
|
||||
|
||||
.macos-switcher-dot {
|
||||
background: alpha(#fff, 0.3);
|
||||
/* border: none; */
|
||||
border-radius: 50%;
|
||||
min-width: 12px; /* Smaller dots */
|
||||
min-width: 12px;
|
||||
/* Smaller dots */
|
||||
min-height: 12px;
|
||||
margin: 2px; /* Reduced margins */
|
||||
margin: 2px;
|
||||
/* Reduced margins */
|
||||
/* transition: all 0.2s ease; */
|
||||
}
|
||||
|
||||
@@ -791,7 +813,8 @@
|
||||
|
||||
.macos-switcher-dot.active {
|
||||
background: #ffffff;
|
||||
box-shadow: 0 0 0 1px alpha(#fff, 0.3); /* Smaller glow */
|
||||
box-shadow: 0 0 0 1px alpha(#fff, 0.3);
|
||||
/* Smaller glow */
|
||||
}
|
||||
|
||||
/* Per app-volume-item */
|
||||
@@ -801,9 +824,11 @@
|
||||
padding-right: 14px;
|
||||
border-bottom: 1px solid alpha(#fff, 0.1);
|
||||
}
|
||||
|
||||
#app-icon {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.app-name-compact {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
@@ -914,6 +939,7 @@
|
||||
#wifi-other-button:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
#wifi-other-button:last-child {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
@@ -922,6 +948,7 @@
|
||||
/* background-color: #000; */
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.device-slot:hover {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
@@ -935,6 +962,7 @@
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#app-volume-header {
|
||||
font-size: 16px;
|
||||
padding-left: 5px;
|
||||
@@ -949,6 +977,7 @@
|
||||
padding: 2px;
|
||||
/* margin-right: 8px; */
|
||||
}
|
||||
|
||||
.wifi-icon-box-connected {
|
||||
background-color: #007aff;
|
||||
border-radius: 50%;
|
||||
@@ -967,6 +996,7 @@
|
||||
font-family: "SF Pro Rounded";
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
@@ -981,8 +1011,9 @@
|
||||
#app-control-box {
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
#caffeine-widget,
|
||||
#flight-widget {
|
||||
padding: 0px 0 5px 0;
|
||||
min-width: 50px;
|
||||
}
|
||||
}
|
||||
@@ -61,9 +61,13 @@ class BluetoothDeviceSlot(CenterBox):
|
||||
def __init__(self, device: BluetoothDevice, **kwargs):
|
||||
super().__init__(h_expand=True, name="device-button", **kwargs)
|
||||
self.device = device
|
||||
self.device.connect("changed", self.on_changed)
|
||||
self.device.connect(
|
||||
"notify::closed", lambda *_: self.device.closed and self.destroy()
|
||||
self._destroyed = False
|
||||
self._signal_ids = []
|
||||
self._signal_ids.append(self.device.connect("changed", self.on_changed))
|
||||
self._signal_ids.append(
|
||||
self.device.connect(
|
||||
"notify::closed", lambda *_: self.device.closed and self.destroy()
|
||||
)
|
||||
)
|
||||
|
||||
self.styles = [
|
||||
@@ -112,6 +116,20 @@ class BluetoothDeviceSlot(CenterBox):
|
||||
self.device_button.connect("leave-notify-event", self.on_button_leave)
|
||||
self.device.emit("changed") # to update display status
|
||||
|
||||
def destroy(self):
|
||||
"""Clean up device signal connections to prevent memory leaks."""
|
||||
if self._destroyed:
|
||||
return
|
||||
self._destroyed = True
|
||||
for sig_id in self._signal_ids:
|
||||
try:
|
||||
self.device.disconnect(sig_id)
|
||||
except Exception:
|
||||
pass
|
||||
self._signal_ids.clear()
|
||||
self.device = None
|
||||
super().destroy()
|
||||
|
||||
def on_button_enter(self, widget, event):
|
||||
self.add_style_class("button-hovered")
|
||||
|
||||
@@ -123,6 +141,8 @@ class BluetoothDeviceSlot(CenterBox):
|
||||
self.device.set_connecting(not self.device.connected)
|
||||
|
||||
def on_changed(self, *_):
|
||||
if self._destroyed or self.device is None:
|
||||
return
|
||||
try:
|
||||
# Update connection and pairing status
|
||||
new_styles = [
|
||||
@@ -197,6 +217,7 @@ class BluetoothConnections(Box):
|
||||
self.refresh_timer = None # Timer for periodic device refresh
|
||||
self._update_in_progress = False # Prevent concurrent updates
|
||||
self._destroyed = False # Track if widget is destroyed
|
||||
self._client_signal_ids = [] # Track BluetoothClient signal IDs
|
||||
|
||||
self.client = BluetoothClient(on_device_added=self.on_device_added)
|
||||
|
||||
@@ -236,19 +257,29 @@ class BluetoothConnections(Box):
|
||||
),
|
||||
)
|
||||
|
||||
# Connect client signals
|
||||
self.client.connect(
|
||||
"notify::enabled",
|
||||
lambda *_: self.toggle_button.set_active(self.client.enabled),
|
||||
# Connect client signals — track IDs for disconnect on destroy
|
||||
self._client_signal_ids.append(
|
||||
self.client.connect(
|
||||
"notify::enabled",
|
||||
lambda *_: self.toggle_button.set_active(self.client.enabled),
|
||||
)
|
||||
)
|
||||
self._client_signal_ids.append(
|
||||
self.client.connect("notify::scanning", lambda *_: self.update_scan_label())
|
||||
)
|
||||
self.client.connect("notify::scanning", lambda *_: self.update_scan_label())
|
||||
|
||||
# Connect to device changes
|
||||
self.client.connect("device-added", self.update_devices)
|
||||
self.client.connect("device-removed", self.update_devices)
|
||||
self._client_signal_ids.append(
|
||||
self.client.connect("device-added", self.update_devices)
|
||||
)
|
||||
self._client_signal_ids.append(
|
||||
self.client.connect("device-removed", self.update_devices)
|
||||
)
|
||||
|
||||
# Connect to additional signals for better real-time monitoring
|
||||
self.client.connect("changed", self.on_client_changed)
|
||||
self._client_signal_ids.append(
|
||||
self.client.connect("changed", self.on_client_changed)
|
||||
)
|
||||
|
||||
# Create Devices section
|
||||
self.paired_devices_label = Label(
|
||||
@@ -473,20 +504,21 @@ class BluetoothConnections(Box):
|
||||
|
||||
def periodic_device_refresh(self):
|
||||
"""Periodically refresh device list to catch external connections"""
|
||||
# Skip if update in progress, destroyed, or client not available
|
||||
# If destroyed, remove the GLib source by returning False
|
||||
if self._destroyed:
|
||||
self.refresh_timer = None
|
||||
return False
|
||||
|
||||
# Skip if update in progress or client not available/enabled
|
||||
if (
|
||||
self._update_in_progress
|
||||
or self._destroyed
|
||||
or not self.client
|
||||
or not self.client.enabled
|
||||
):
|
||||
return True # Continue monitoring
|
||||
|
||||
try:
|
||||
# Simple check - just trigger update_devices which has its own safety checks
|
||||
# Don't force signal emissions as that can cause race conditions
|
||||
self.update_devices()
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -513,13 +545,20 @@ class BluetoothConnections(Box):
|
||||
"""Cleanup when widget is destroyed"""
|
||||
# Mark as destroyed to prevent further updates
|
||||
self._destroyed = True
|
||||
# Stop monitoring
|
||||
# Stop monitoring timer
|
||||
self.stop_device_monitoring()
|
||||
# Make sure other devices revealer is collapsed when closing
|
||||
# Disconnect all BluetoothClient signals
|
||||
for sig_id in self._client_signal_ids:
|
||||
try:
|
||||
self.client.disconnect(sig_id)
|
||||
except Exception:
|
||||
pass
|
||||
self._client_signal_ids.clear()
|
||||
# Collapse other devices revealer
|
||||
try:
|
||||
self.other_devices_revealer.child_revealed = False
|
||||
except Exception:
|
||||
pass # Widget might already be destroyed
|
||||
pass
|
||||
|
||||
def close_bluetooth(self):
|
||||
"""Called when Bluetooth panel is being closed"""
|
||||
|
||||
@@ -61,9 +61,13 @@ class BluetoothDeviceSlot(CenterBox):
|
||||
def __init__(self, device: BluetoothDevice, **kwargs):
|
||||
super().__init__(h_expand=True, name="device-button", **kwargs)
|
||||
self.device = device
|
||||
self.device.connect("changed", self.on_changed)
|
||||
self.device.connect(
|
||||
"notify::closed", lambda *_: self.device.closed and self.destroy()
|
||||
self._destroyed = False
|
||||
self._signal_ids = []
|
||||
self._signal_ids.append(self.device.connect("changed", self.on_changed))
|
||||
self._signal_ids.append(
|
||||
self.device.connect(
|
||||
"notify::closed", lambda *_: self.device.closed and self.destroy()
|
||||
)
|
||||
)
|
||||
|
||||
self.styles = [
|
||||
@@ -112,6 +116,20 @@ class BluetoothDeviceSlot(CenterBox):
|
||||
self.device_button.connect("leave-notify-event", self.on_button_leave)
|
||||
self.device.emit("changed") # to update display status
|
||||
|
||||
def destroy(self):
|
||||
"""Clean up device signal connections to prevent memory leaks."""
|
||||
if self._destroyed:
|
||||
return
|
||||
self._destroyed = True
|
||||
for sig_id in self._signal_ids:
|
||||
try:
|
||||
self.device.disconnect(sig_id)
|
||||
except Exception:
|
||||
pass
|
||||
self._signal_ids.clear()
|
||||
self.device = None
|
||||
super().destroy()
|
||||
|
||||
def on_button_enter(self, widget, event):
|
||||
self.add_style_class("button-hovered")
|
||||
|
||||
@@ -123,6 +141,8 @@ class BluetoothDeviceSlot(CenterBox):
|
||||
self.device.set_connecting(not self.device.connected)
|
||||
|
||||
def on_changed(self, *_):
|
||||
if self._destroyed or self.device is None:
|
||||
return
|
||||
try:
|
||||
# Update connection and pairing status
|
||||
new_styles = [
|
||||
@@ -197,6 +217,7 @@ class BluetoothConnections(Box):
|
||||
self.refresh_timer = None # Timer for periodic device refresh
|
||||
self._update_in_progress = False # Prevent concurrent updates
|
||||
self._destroyed = False # Track if widget is destroyed
|
||||
self._client_signal_ids = [] # Track BluetoothClient signal IDs
|
||||
|
||||
self.client = BluetoothClient(on_device_added=self.on_device_added)
|
||||
|
||||
@@ -236,19 +257,29 @@ class BluetoothConnections(Box):
|
||||
),
|
||||
)
|
||||
|
||||
# Connect client signals
|
||||
self.client.connect(
|
||||
"notify::enabled",
|
||||
lambda *_: self.toggle_button.set_active(self.client.enabled),
|
||||
# Connect client signals — track IDs for disconnect on destroy
|
||||
self._client_signal_ids.append(
|
||||
self.client.connect(
|
||||
"notify::enabled",
|
||||
lambda *_: self.toggle_button.set_active(self.client.enabled),
|
||||
)
|
||||
)
|
||||
self._client_signal_ids.append(
|
||||
self.client.connect("notify::scanning", lambda *_: self.update_scan_label())
|
||||
)
|
||||
self.client.connect("notify::scanning", lambda *_: self.update_scan_label())
|
||||
|
||||
# Connect to device changes
|
||||
self.client.connect("device-added", self.update_devices)
|
||||
self.client.connect("device-removed", self.update_devices)
|
||||
self._client_signal_ids.append(
|
||||
self.client.connect("device-added", self.update_devices)
|
||||
)
|
||||
self._client_signal_ids.append(
|
||||
self.client.connect("device-removed", self.update_devices)
|
||||
)
|
||||
|
||||
# Connect to additional signals for better real-time monitoring
|
||||
self.client.connect("changed", self.on_client_changed)
|
||||
self._client_signal_ids.append(
|
||||
self.client.connect("changed", self.on_client_changed)
|
||||
)
|
||||
|
||||
# Create Devices section
|
||||
self.paired_devices_label = Label(
|
||||
@@ -473,20 +504,21 @@ class BluetoothConnections(Box):
|
||||
|
||||
def periodic_device_refresh(self):
|
||||
"""Periodically refresh device list to catch external connections"""
|
||||
# Skip if update in progress, destroyed, or client not available
|
||||
# If destroyed, remove the GLib source by returning False
|
||||
if self._destroyed:
|
||||
self.refresh_timer = None
|
||||
return False
|
||||
|
||||
# Skip if update in progress or client not available/enabled
|
||||
if (
|
||||
self._update_in_progress
|
||||
or self._destroyed
|
||||
or not self.client
|
||||
or not self.client.enabled
|
||||
):
|
||||
return True # Continue monitoring
|
||||
|
||||
try:
|
||||
# Simple check - just trigger update_devices which has its own safety checks
|
||||
# Don't force signal emissions as that can cause race conditions
|
||||
self.update_devices()
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -513,13 +545,20 @@ class BluetoothConnections(Box):
|
||||
"""Cleanup when widget is destroyed"""
|
||||
# Mark as destroyed to prevent further updates
|
||||
self._destroyed = True
|
||||
# Stop monitoring
|
||||
# Stop monitoring timer
|
||||
self.stop_device_monitoring()
|
||||
# Make sure other devices revealer is collapsed when closing
|
||||
# Disconnect all BluetoothClient signals
|
||||
for sig_id in self._client_signal_ids:
|
||||
try:
|
||||
self.client.disconnect(sig_id)
|
||||
except Exception:
|
||||
pass
|
||||
self._client_signal_ids.clear()
|
||||
# Collapse other devices revealer
|
||||
try:
|
||||
self.other_devices_revealer.child_revealed = False
|
||||
except Exception:
|
||||
pass # Widget might already be destroyed
|
||||
pass
|
||||
|
||||
def close_bluetooth(self):
|
||||
"""Called when Bluetooth panel is being closed"""
|
||||
|
||||
@@ -509,14 +509,14 @@ class ModusControlCenter(Window):
|
||||
# Check initial states lazily (only when needed)
|
||||
self._check_initial_states()
|
||||
|
||||
# Store signal connections for cleanup
|
||||
# Store signal connections as (obj, handler_id) tuples for proper cleanup
|
||||
self._signal_connections.extend(
|
||||
[
|
||||
audio_service.connect("changed", self.audio_changed),
|
||||
audio_service.connect("changed", self.volume_changed),
|
||||
modus_service.connect("wlan-changed", self.wlan_changed),
|
||||
modus_service.connect("bluetooth-changed", self.bluetooth_changed),
|
||||
modus_service.connect("dont-disturb-changed", self.dnd_changed),
|
||||
(audio_service, audio_service.connect("changed", self.audio_changed)),
|
||||
(audio_service, audio_service.connect("changed", self.volume_changed)),
|
||||
(modus_service, modus_service.connect("wlan-changed", self.wlan_changed)),
|
||||
(modus_service, modus_service.connect("bluetooth-changed", self.bluetooth_changed)),
|
||||
(modus_service, modus_service.connect("dont-disturb-changed", self.dnd_changed)),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -525,7 +525,7 @@ class ModusControlCenter(Window):
|
||||
self.brightness_scale.connect("change-value", self.set_brightness)
|
||||
self.brightness_scale.connect("scroll-event", self.on_brightness_scroll)
|
||||
self._signal_connections.append(
|
||||
brightness_service.connect("screen", self.brightness_changed)
|
||||
(brightness_service, brightness_service.connect("screen", self.brightness_changed))
|
||||
)
|
||||
|
||||
# Connect volume scale signals
|
||||
@@ -970,70 +970,42 @@ class ModusControlCenter(Window):
|
||||
def _disconnect_all_signals(self):
|
||||
"""Disconnect all signal connections to prevent memory leaks"""
|
||||
try:
|
||||
for connection in self._signal_connections:
|
||||
# _signal_connections stores (obj, handler_id) tuples
|
||||
for obj, handler_id in self._signal_connections:
|
||||
try:
|
||||
if connection and hasattr(connection, "disconnect"):
|
||||
connection.disconnect()
|
||||
obj.disconnect(handler_id)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to disconnect signal: {e}")
|
||||
|
||||
self._signal_connections.clear()
|
||||
|
||||
try:
|
||||
self.disconnect_by_func(self._on_visibility_changed)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to disconnect visibility signal: {e}")
|
||||
|
||||
try:
|
||||
self.disconnect_by_func(self.hide_controlcenter)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to disconnect keybinding: {e}")
|
||||
|
||||
if hasattr(self, "network_service") and self.network_service:
|
||||
try:
|
||||
self.network_service.disconnect_by_func(self.on_network_ready)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to disconnect network signal: {e}")
|
||||
|
||||
if hasattr(self, "brightness_service") and self.brightness_service:
|
||||
try:
|
||||
self.brightness_service.disconnect_by_func(self.brightness_changed)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to disconnect brightness signal: {e}")
|
||||
|
||||
if hasattr(self, "audio_service") and audio_service:
|
||||
try:
|
||||
audio_service.disconnect_by_func(self.audio_changed)
|
||||
audio_service.disconnect_by_func(self.volume_changed)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to disconnect audio signals: {e}")
|
||||
|
||||
if hasattr(self, "modus_service") and modus_service:
|
||||
try:
|
||||
modus_service.disconnect_by_func(self.wlan_changed)
|
||||
modus_service.disconnect_by_func(self.bluetooth_changed)
|
||||
modus_service.disconnect_by_func(self.dnd_changed)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to disconnect modus signals: {e}")
|
||||
|
||||
# Disconnect scale widget signals (connected directly, not tracked)
|
||||
if hasattr(self, "volume_scale") and self.volume_scale:
|
||||
try:
|
||||
self.volume_scale.disconnect_by_func(self.set_volume)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
self.volume_scale.disconnect_by_func(self.on_volume_scroll)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to disconnect volume scale signals: {e}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if hasattr(self, "brightness_scale") and self.brightness_scale:
|
||||
try:
|
||||
self.brightness_scale.disconnect_by_func(self.set_brightness)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
self.brightness_scale.disconnect_by_func(self.on_brightness_scroll)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Failed to disconnect brightness scale signals: {e}"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Disconnect the visibility change signal on self
|
||||
try:
|
||||
self.disconnect_by_func(self._on_visibility_changed)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self._signals_connected = False
|
||||
|
||||
logger.debug("All signals disconnected successfully")
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -26,6 +26,8 @@ class PerAppVolumeControl(Box):
|
||||
self._updating_volumes = set()
|
||||
self._app_widgets = {}
|
||||
self._signal_connections = []
|
||||
self._destroyed = False
|
||||
self._refresh_timer = None
|
||||
|
||||
# Header with back button
|
||||
self.header = Box(
|
||||
@@ -80,6 +82,9 @@ class PerAppVolumeControl(Box):
|
||||
|
||||
def _auto_refresh(self):
|
||||
"""Auto-refresh the application list every 2 seconds"""
|
||||
if self._destroyed:
|
||||
self._refresh_timer = None
|
||||
return False # Remove GLib source
|
||||
self._populate_apps()
|
||||
return True # Continue the timer
|
||||
|
||||
@@ -306,7 +311,8 @@ class PerAppVolumeControl(Box):
|
||||
|
||||
def _on_stream_changed(self, *_):
|
||||
"""Handle when audio streams are added or removed"""
|
||||
GLib.idle_add(self._populate_apps)
|
||||
if not self._destroyed:
|
||||
GLib.idle_add(self._populate_apps)
|
||||
|
||||
def refresh(self):
|
||||
"""Manually refresh the application list"""
|
||||
@@ -314,10 +320,17 @@ class PerAppVolumeControl(Box):
|
||||
|
||||
def destroy(self):
|
||||
"""Clean up resources"""
|
||||
if hasattr(self, "_refresh_timer"):
|
||||
GLib.source_remove(self._refresh_timer)
|
||||
self._destroyed = True
|
||||
|
||||
# Disconnect fabric audio service signals
|
||||
# Stop the auto-refresh timer
|
||||
if self._refresh_timer is not None:
|
||||
try:
|
||||
GLib.source_remove(self._refresh_timer)
|
||||
except Exception:
|
||||
pass
|
||||
self._refresh_timer = None
|
||||
|
||||
# Disconnect audio service signals
|
||||
if audio_service:
|
||||
for connection in self._signal_connections:
|
||||
try:
|
||||
@@ -328,5 +341,6 @@ class PerAppVolumeControl(Box):
|
||||
self._signal_connections.clear()
|
||||
self._app_widgets.clear()
|
||||
self._updating_volumes.clear()
|
||||
self.control_center = None
|
||||
|
||||
super().destroy()
|
||||
@@ -262,9 +262,12 @@ class WifiConnections(Box):
|
||||
self.refresh_timer = None # Timer for periodic network refresh
|
||||
self._update_in_progress = False # Prevent concurrent updates
|
||||
self._destroyed = False # Track if widget is destroyed
|
||||
self._signal_ids = [] # Track all service signal IDs for cleanup
|
||||
|
||||
# Wait for network service to be ready
|
||||
self.network_service.connect("wifi-device-added", self.on_network_ready)
|
||||
# Wait for network service to be ready — track signal ID
|
||||
self._signal_ids.append(
|
||||
(self.network_service, self.network_service.connect("wifi-device-added", self.on_network_ready))
|
||||
)
|
||||
|
||||
# Create pull-to-refresh indicator
|
||||
self.refresh_indicator = Label(
|
||||
@@ -387,13 +390,21 @@ class WifiConnections(Box):
|
||||
self.toggle_button.set_active(self.wifi_service.wireless_enabled)
|
||||
self.toggle_button.connect("notify::active", self.on_toggle_changed)
|
||||
|
||||
# Connect to WiFi service signals
|
||||
self.wifi_service.connect(
|
||||
"notify::wireless-enabled", self.on_wifi_enabled_changed
|
||||
# Connect to WiFi service signals — track IDs for cleanup
|
||||
self._signal_ids.append(
|
||||
(self.wifi_service, self.wifi_service.connect(
|
||||
"notify::wireless-enabled", self.on_wifi_enabled_changed
|
||||
))
|
||||
)
|
||||
self._signal_ids.append(
|
||||
(self.wifi_service, self.wifi_service.connect("changed", self.update_networks))
|
||||
)
|
||||
self._signal_ids.append(
|
||||
(self.wifi_service, self.wifi_service.connect("ap-added", self.update_networks))
|
||||
)
|
||||
self._signal_ids.append(
|
||||
(self.wifi_service, self.wifi_service.connect("ap-removed", self.update_networks))
|
||||
)
|
||||
self.wifi_service.connect("changed", self.update_networks)
|
||||
self.wifi_service.connect("ap-added", self.update_networks)
|
||||
self.wifi_service.connect("ap-removed", self.update_networks)
|
||||
|
||||
# Initial network update
|
||||
self.update_networks()
|
||||
@@ -581,17 +592,20 @@ class WifiConnections(Box):
|
||||
|
||||
def periodic_network_refresh(self):
|
||||
"""Periodically refresh network list to catch external connections"""
|
||||
# Skip if update in progress, destroyed, or wifi service not available
|
||||
# If destroyed, remove the GLib source by returning False
|
||||
if self._destroyed:
|
||||
self.refresh_timer = None
|
||||
return False
|
||||
|
||||
# Skip if update in progress or wifi not available/enabled
|
||||
if (
|
||||
self._update_in_progress
|
||||
or self._destroyed
|
||||
or not self.wifi_service
|
||||
or not self.wifi_service.wireless_enabled
|
||||
):
|
||||
return True # Continue monitoring
|
||||
|
||||
try:
|
||||
# Simple check - just trigger update_networks which has its own safety checks
|
||||
self.update_networks()
|
||||
except Exception:
|
||||
pass
|
||||
@@ -692,13 +706,23 @@ class WifiConnections(Box):
|
||||
"""Cleanup when widget is destroyed"""
|
||||
# Mark as destroyed to prevent further updates
|
||||
self._destroyed = True
|
||||
# Stop monitoring
|
||||
# Stop monitoring timer
|
||||
self.stop_network_monitoring()
|
||||
# Make sure other networks revealer is collapsed when closing
|
||||
# Disconnect all tracked service signals
|
||||
for obj, sig_id in self._signal_ids:
|
||||
try:
|
||||
obj.disconnect(sig_id)
|
||||
except Exception:
|
||||
pass
|
||||
self._signal_ids.clear()
|
||||
# Drop strong references
|
||||
self.wifi_service = None
|
||||
self.network_service = None
|
||||
# Collapse other networks revealer
|
||||
try:
|
||||
self.other_networks_revealer.child_revealed = False
|
||||
except Exception:
|
||||
pass # Widget might already be destroyed
|
||||
pass
|
||||
|
||||
def close_wifi(self):
|
||||
"""Called when WiFi panel is being closed"""
|
||||
|
||||
Reference in new issue
Block a user