related to memory leak
This commit is contained in:
25 files changed
+610
-68
No files matched your search
@@ -510,11 +510,7 @@ class BluetoothConnections(Box):
|
||||
return False
|
||||
|
||||
# Skip if update in progress or client not available/enabled
|
||||
if (
|
||||
self._update_in_progress
|
||||
or not self.client
|
||||
or not self.client.enabled
|
||||
):
|
||||
if self._update_in_progress or not self.client or not self.client.enabled:
|
||||
return True # Continue monitoring
|
||||
|
||||
try:
|
||||
|
||||
@@ -510,11 +510,7 @@ class BluetoothConnections(Box):
|
||||
return False
|
||||
|
||||
# Skip if update in progress or client not available/enabled
|
||||
if (
|
||||
self._update_in_progress
|
||||
or not self.client
|
||||
or not self.client.enabled
|
||||
):
|
||||
if self._update_in_progress or not self.client or not self.client.enabled:
|
||||
return True # Continue monitoring
|
||||
|
||||
try:
|
||||
|
||||
@@ -184,6 +184,20 @@ class EmbeddedExpandedPlayer(Box):
|
||||
# This will automatically update as MPRIS players change
|
||||
pass
|
||||
|
||||
def suspend(self):
|
||||
"""Suspend updates when control center is hidden"""
|
||||
if hasattr(self, "player_stack") and self.player_stack:
|
||||
for child in self.player_stack.get_children():
|
||||
if hasattr(child, "suspend"):
|
||||
child.suspend()
|
||||
|
||||
def resume(self):
|
||||
"""Resume updates when control center is shown"""
|
||||
if hasattr(self, "player_stack") and self.player_stack:
|
||||
for child in self.player_stack.get_children():
|
||||
if hasattr(child, "resume"):
|
||||
child.resume()
|
||||
|
||||
def destroy(self):
|
||||
"""Clean up resources and prevent memory leaks"""
|
||||
logger.debug("🗑️ EmbeddedExpandedPlayer cleanup starting")
|
||||
@@ -787,7 +801,9 @@ class PlayerBox(Box):
|
||||
"label",
|
||||
GObject.BindingFlags.DEFAULT,
|
||||
lambda _, x: (
|
||||
re.sub(r"\r?\n", " ", x) if x != "" and x is not None else "No Title"
|
||||
re.sub(r"\r?\n", " ", x)
|
||||
if x != "" and x is not None
|
||||
else "No Title"
|
||||
), # type: ignore
|
||||
)
|
||||
)
|
||||
@@ -811,7 +827,9 @@ class PlayerBox(Box):
|
||||
"label",
|
||||
GObject.BindingFlags.DEFAULT,
|
||||
lambda _, x: (
|
||||
re.sub(r"\r?\n", " ", x) if x != "" and x is not None else "No Album"
|
||||
re.sub(r"\r?\n", " ", x)
|
||||
if x != "" and x is not None
|
||||
else "No Album"
|
||||
), # type: ignore
|
||||
)
|
||||
)
|
||||
@@ -1097,6 +1115,27 @@ class PlayerBox(Box):
|
||||
# Start new timer and store its ID
|
||||
self._seekbar_timer_id = invoke_repeater(1000, self._move_seekbar)
|
||||
|
||||
def suspend(self):
|
||||
"""Stop timer and other active updates when hidden"""
|
||||
if self._seekbar_timer_id:
|
||||
try:
|
||||
GLib.source_remove(self._seekbar_timer_id)
|
||||
except Exception:
|
||||
pass
|
||||
self._seekbar_timer_id = None
|
||||
logger.debug(
|
||||
f"[PlayerBox] Suspended timer for {self.player.player_name if self.player else 'unknown'}"
|
||||
)
|
||||
|
||||
def resume(self):
|
||||
"""Restart timer and updates when shown"""
|
||||
if self.exit or self.player is None:
|
||||
return
|
||||
|
||||
if not self._seekbar_timer_id:
|
||||
self._seekbar_timer_id = invoke_repeater(1000, self._move_seekbar)
|
||||
logger.debug(f"[PlayerBox] Resumed timer for {self.player.player_name}")
|
||||
|
||||
def _cleanup_temp_files(self):
|
||||
"""Clean up temporary artwork files."""
|
||||
for temp_file in self.temp_artwork_files:
|
||||
|
||||
@@ -8,6 +8,10 @@ from fabric.widgets.label import Label
|
||||
from fabric.widgets.scale import Scale
|
||||
from fabric.widgets.wayland import WaylandWindow as Window
|
||||
|
||||
from services.brightness import Brightness
|
||||
from services.network import NetworkClient
|
||||
from utils.roam import audio_service, modus_service
|
||||
from utils.utils import svg_file
|
||||
from window.controlcenter.bluetooth import (
|
||||
BluetoothConnections,
|
||||
set_bluetooth_enabled_with_fallback,
|
||||
@@ -17,10 +21,6 @@ from window.controlcenter.nightlight import create_night_light_widget
|
||||
from window.controlcenter.per_app_volume import PerAppVolumeControl
|
||||
from window.controlcenter.player import PlayerBoxStack, get_shared_mpris_manager
|
||||
from window.controlcenter.wifi import WifiConnections
|
||||
from services.brightness import Brightness
|
||||
from services.network import NetworkClient
|
||||
from utils.roam import audio_service, modus_service
|
||||
from utils.utils import svg_file
|
||||
|
||||
brightness_service = Brightness.get_initial()
|
||||
|
||||
@@ -487,11 +487,27 @@ class ModusControlCenter(Window):
|
||||
def _on_visibility_changed(self, widget, param):
|
||||
"""Handle visibility changes for resource management"""
|
||||
if not self.get_visible():
|
||||
# Suspend player updates
|
||||
if hasattr(self, "music_widget") and hasattr(self.music_widget, "suspend"):
|
||||
self.music_widget.suspend()
|
||||
if hasattr(self, "_expanded_player_widget") and hasattr(
|
||||
self._expanded_player_widget, "suspend"
|
||||
):
|
||||
self._expanded_player_widget.suspend()
|
||||
|
||||
# Just disconnect signals and reset state flags - don't destroy widgets
|
||||
self._disconnect_signals_when_hidden()
|
||||
else:
|
||||
self._initialize_resources()
|
||||
|
||||
# Resume player updates
|
||||
if hasattr(self, "music_widget") and hasattr(self.music_widget, "resume"):
|
||||
self.music_widget.resume()
|
||||
if hasattr(self, "_expanded_player_widget") and hasattr(
|
||||
self._expanded_player_widget, "resume"
|
||||
):
|
||||
self._expanded_player_widget.resume()
|
||||
|
||||
def _initialize_resources(self):
|
||||
"""Initialize resources and connect signals when the control center becomes visible."""
|
||||
if self._resources_initialized:
|
||||
@@ -512,11 +528,28 @@ class ModusControlCenter(Window):
|
||||
# Store signal connections as (obj, handler_id) tuples for proper cleanup
|
||||
self._signal_connections.extend(
|
||||
[
|
||||
(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)),
|
||||
(
|
||||
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 +558,10 @@ 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, brightness_service.connect("screen", self.brightness_changed))
|
||||
(
|
||||
brightness_service,
|
||||
brightness_service.connect("screen", self.brightness_changed),
|
||||
)
|
||||
)
|
||||
|
||||
# Connect volume scale signals
|
||||
@@ -547,7 +583,28 @@ class ModusControlCenter(Window):
|
||||
def _disconnect_signals_when_hidden(self):
|
||||
"""Disconnect signals when hidden to reduce resource usage, but keep widgets intact"""
|
||||
try:
|
||||
# Just disconnect signals and reset flags - don't destroy widgets
|
||||
# Actually disconnect the signals we tracked
|
||||
for obj, handler_id in self._signal_connections:
|
||||
try:
|
||||
obj.disconnect(handler_id)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to disconnect signal: {e}")
|
||||
self._signal_connections.clear()
|
||||
|
||||
# Disconnect direct scale signals (connected in _initialize_resources)
|
||||
if hasattr(self, "volume_scale"):
|
||||
try:
|
||||
self.volume_scale.disconnect_by_func(self.set_volume)
|
||||
self.volume_scale.disconnect_by_func(self.on_volume_scroll)
|
||||
except Exception:
|
||||
pass
|
||||
if hasattr(self, "brightness_scale"):
|
||||
try:
|
||||
self.brightness_scale.disconnect_by_func(self.set_brightness)
|
||||
self.brightness_scale.disconnect_by_func(self.on_brightness_scroll)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self._signals_connected = False
|
||||
self._resources_initialized = False
|
||||
|
||||
@@ -560,6 +617,8 @@ class ModusControlCenter(Window):
|
||||
# Reset current view
|
||||
self.current_view = "main"
|
||||
|
||||
logger.debug("Control center signals disconnected while hidden")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Control center signal disconnection failed: {e}")
|
||||
|
||||
|
||||
@@ -170,7 +170,12 @@ class PerAppVolumeControl(Box):
|
||||
|
||||
def _populate_apps(self):
|
||||
"""Populate the widget with current audio applications"""
|
||||
# Clear existing widgets
|
||||
# Clear and DESTROY existing widgets to prevent memory leaks
|
||||
for child in list(self.apps_container.get_children()):
|
||||
try:
|
||||
child.destroy()
|
||||
except Exception:
|
||||
pass
|
||||
self.apps_container.children = []
|
||||
self._app_widgets.clear()
|
||||
|
||||
|
||||
@@ -259,6 +259,20 @@ class PlayerBoxStack(Box):
|
||||
|
||||
super().destroy()
|
||||
|
||||
def suspend(self):
|
||||
"""Suspend all child players when hidden"""
|
||||
for child in self.player_stack.get_children():
|
||||
if hasattr(child, "suspend"):
|
||||
child.suspend()
|
||||
logger.debug("[PlayerBoxStack] Suspended child players")
|
||||
|
||||
def resume(self):
|
||||
"""Resume all child players when shown"""
|
||||
for child in self.player_stack.get_children():
|
||||
if hasattr(child, "resume"):
|
||||
child.resume()
|
||||
logger.debug("[PlayerBoxStack] Resumed child players")
|
||||
|
||||
def _periodic_cleanup(self):
|
||||
"""Enhanced cleanup for reuse - clean internal state and free memory"""
|
||||
try:
|
||||
@@ -776,7 +790,9 @@ class PlayerBox(Box):
|
||||
"label",
|
||||
GObject.BindingFlags.DEFAULT,
|
||||
lambda _, x: (
|
||||
re.sub(r"\r?\n", " ", x) if x != "" and x is not None else "No Title"
|
||||
re.sub(r"\r?\n", " ", x)
|
||||
if x != "" and x is not None
|
||||
else "No Title"
|
||||
), # type: ignore
|
||||
)
|
||||
)
|
||||
@@ -904,6 +920,37 @@ class PlayerBox(Box):
|
||||
for handler_id in connections:
|
||||
self._signal_connections.append((self.player, handler_id))
|
||||
|
||||
def suspend(self):
|
||||
"""Disconnect signals when hidden"""
|
||||
self.exit = True
|
||||
for obj, handler_id in self._signal_connections:
|
||||
try:
|
||||
obj.disconnect(handler_id)
|
||||
except Exception:
|
||||
pass
|
||||
self._signal_connections.clear()
|
||||
logger.debug(
|
||||
f"[PlayerBox] Suspended signals for {self.player.player_name if self.player else 'unknown'}"
|
||||
)
|
||||
|
||||
def resume(self):
|
||||
"""Reconnect signals when shown"""
|
||||
if self.player is None:
|
||||
return
|
||||
self.exit = False
|
||||
if not self._signal_connections:
|
||||
connections = bulk_connect(
|
||||
self.player,
|
||||
{
|
||||
"closed": self._on_player_exit,
|
||||
"notify::playback-status": self._on_playback_change,
|
||||
"notify::metadata": self._on_metadata,
|
||||
},
|
||||
)
|
||||
for handler_id in connections:
|
||||
self._signal_connections.append((self.player, handler_id))
|
||||
logger.debug(f"[PlayerBox] Resumed signals for {self.player.player_name}")
|
||||
|
||||
def destroy(self):
|
||||
"""Clean up all resources when the widget is destroyed."""
|
||||
# Set exit flag first — stops all callbacks from doing work
|
||||
|
||||
@@ -266,7 +266,12 @@ class WifiConnections(Box):
|
||||
|
||||
# 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))
|
||||
(
|
||||
self.network_service,
|
||||
self.network_service.connect(
|
||||
"wifi-device-added", self.on_network_ready
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
# Create pull-to-refresh indicator
|
||||
@@ -392,18 +397,30 @@ class WifiConnections(Box):
|
||||
|
||||
# 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.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.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.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,
|
||||
self.wifi_service.connect("ap-removed", self.update_networks),
|
||||
)
|
||||
)
|
||||
|
||||
# Initial network update
|
||||
|
||||
Reference in new issue
Block a user