Enchancements
This commit is contained in:
1 parent
e44d2aef00
commit
8c56d9b236
6 files changed
+131
-120
No files matched your search
@@ -18,6 +18,7 @@ from fabric.utils.helpers import get_relative_path
|
||||
from fabric.widgets.image import Image
|
||||
from fabric.widgets.overlay import Overlay
|
||||
from fabric.widgets.stack import Stack
|
||||
from fabric.widgets.svg import Svg
|
||||
from gi.repository import GLib, GObject
|
||||
from loguru import logger
|
||||
|
||||
@@ -159,7 +160,7 @@ class PlayerBoxStack(Box):
|
||||
|
||||
self.mpris_manager = mpris_manager
|
||||
|
||||
# Track connections for cleanup
|
||||
# Track connections for cleanup - store (object, handler_id) tuples
|
||||
connections = bulk_connect(
|
||||
self.mpris_manager,
|
||||
{
|
||||
@@ -167,7 +168,9 @@ class PlayerBoxStack(Box):
|
||||
"player-vanished": self.on_lost_player,
|
||||
},
|
||||
)
|
||||
self._signal_connections.extend(connections)
|
||||
# Store as (object, handler_id) tuples
|
||||
for handler_id in connections:
|
||||
self._signal_connections.append((self.mpris_manager, handler_id))
|
||||
|
||||
for player in self.mpris_manager.players: # type: ignore
|
||||
logger.info(
|
||||
@@ -178,9 +181,9 @@ class PlayerBoxStack(Box):
|
||||
def destroy(self):
|
||||
"""Clean up resources when the widget is destroyed."""
|
||||
# Disconnect all signal connections
|
||||
for connection in self._signal_connections:
|
||||
for obj, handler_id in self._signal_connections:
|
||||
try:
|
||||
connection.disconnect()
|
||||
obj.disconnect(handler_id)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to disconnect signal: {e}")
|
||||
self._signal_connections.clear()
|
||||
@@ -688,21 +691,24 @@ class PlayerBox(Box):
|
||||
self.stack_buttons_box.hide() # Initially hidden
|
||||
|
||||
# Create SVG icons from player directory
|
||||
self.skip_next_icon = Image(
|
||||
image_file=get_relative_path("../../config/assets/icons/player/fwd.svg"),
|
||||
icon_size=16,
|
||||
self.skip_next_icon = Svg(
|
||||
name="btn",
|
||||
style_classes=["control-buttons"],
|
||||
svg_file=get_relative_path("../../config/assets/icons/player/fwd.svg"),
|
||||
)
|
||||
self.skip_prev_icon = Image(
|
||||
image_file=get_relative_path("../../config/assets/icons/player/Rewind.svg"),
|
||||
icon_size=16,
|
||||
self.skip_prev_icon = Svg(
|
||||
name="btn",
|
||||
style_classes=["control-buttons"],
|
||||
svg_file=get_relative_path("../../config/assets/icons/player/Rewind.svg"),
|
||||
)
|
||||
self.shuffle_icon = Image(icon_name="shuffle") # Keep existing shuffle icon
|
||||
self.play_pause_icon = Image(
|
||||
image_file=get_relative_path("../../config/assets/icons/player/Pause.svg"),
|
||||
icon_size=20,
|
||||
self.play_pause_icon = Svg(
|
||||
name="btn",
|
||||
style_classes=["control-buttons"],
|
||||
svg_file=get_relative_path("../../config/assets/icons/player/Pause.svg"),
|
||||
)
|
||||
|
||||
self.play_pause_button = Button(
|
||||
style_classes=["control-buttons"],
|
||||
name="macos-play-button",
|
||||
child=self.play_pause_icon,
|
||||
on_clicked=self.player.play_pause,
|
||||
@@ -711,6 +717,7 @@ class PlayerBox(Box):
|
||||
self.player.bind_property("can_pause", self.play_pause_button, "sensitive")
|
||||
|
||||
self.next_button = Button(
|
||||
style_classes=["control-buttons"],
|
||||
name="macos-control-button",
|
||||
child=self.skip_next_icon,
|
||||
on_clicked=self._on_player_next,
|
||||
@@ -720,6 +727,7 @@ class PlayerBox(Box):
|
||||
self.prev_button = Button(
|
||||
name="macos-control-button",
|
||||
child=self.skip_prev_icon,
|
||||
style_classes=["control-buttons"],
|
||||
on_clicked=self._on_player_prev,
|
||||
)
|
||||
self.button_box.children = (
|
||||
@@ -762,7 +770,7 @@ class PlayerBox(Box):
|
||||
|
||||
self.children = [*self.children, self.outer_box]
|
||||
|
||||
# Track signal connections for cleanup
|
||||
# Track signal connections for cleanup - store (object, handler_id) tuples
|
||||
connections = bulk_connect(
|
||||
self.player,
|
||||
{
|
||||
@@ -772,10 +780,15 @@ class PlayerBox(Box):
|
||||
"notify::metadata": self._on_metadata,
|
||||
},
|
||||
)
|
||||
self._signal_connections.extend(connections)
|
||||
# Store as (object, handler_id) tuples
|
||||
for handler_id in connections:
|
||||
self._signal_connections.append((self.player, handler_id))
|
||||
|
||||
def destroy(self):
|
||||
"""Clean up all resources when the widget is destroyed."""
|
||||
# Set exit flag FIRST to stop any running timers
|
||||
self.exit = True
|
||||
|
||||
# Cancel any ongoing downloads immediately
|
||||
self._download_cancelled = True
|
||||
|
||||
@@ -787,9 +800,9 @@ class PlayerBox(Box):
|
||||
pass
|
||||
|
||||
# Disconnect all signal connections
|
||||
for connection in self._signal_connections:
|
||||
for obj, handler_id in self._signal_connections:
|
||||
try:
|
||||
connection.disconnect()
|
||||
obj.disconnect(handler_id)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to disconnect signal: {e}")
|
||||
self._signal_connections.clear()
|
||||
@@ -1047,15 +1060,29 @@ class PlayerBox(Box):
|
||||
if self.player is None or self.exit or self._user_seeking:
|
||||
return True # Continue the timer but don't update while user is seeking
|
||||
|
||||
position = self.player.position
|
||||
self.position_label.set_label(self.length_str(position))
|
||||
# Additional safety checks to prevent GTK errors
|
||||
if not hasattr(self, "seek_bar") or self.seek_bar is None:
|
||||
return False # Stop the timer
|
||||
|
||||
# Only update seek bar if user is not currently seeking
|
||||
if not self._user_seeking:
|
||||
# Clamp position to avoid 32-bit integer overflow
|
||||
max_int32 = 2147483647 # 2^31 - 1
|
||||
safe_position = min(max_int32, position) if position else 0
|
||||
self.seek_bar.set_value(safe_position)
|
||||
try:
|
||||
# Check if the seek bar widget is still valid
|
||||
if not self.seek_bar.get_realized():
|
||||
return False # Widget is destroyed, stop timer
|
||||
|
||||
position = self.player.position
|
||||
self.position_label.set_label(self.length_str(position))
|
||||
|
||||
# Only update seek bar if user is not currently seeking
|
||||
if not self._user_seeking:
|
||||
# Clamp position to avoid 32-bit integer overflow
|
||||
max_int32 = 2147483647 # 2^31 - 1
|
||||
safe_position = min(max_int32, position) if position else 0
|
||||
self.seek_bar.set_value(safe_position)
|
||||
|
||||
except Exception as e:
|
||||
# If any error occurs (widget destroyed, etc), stop the timer
|
||||
logger.warning(f"Seek bar update failed, stopping timer: {e}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@@ -1072,26 +1099,30 @@ class PlayerBox(Box):
|
||||
def _on_scale_value_changed(self, scale: Scale):
|
||||
"""Handle seek bar value changes - only when user is seeking"""
|
||||
if self.player and not self.exit and self._user_seeking:
|
||||
new_position = int(scale.get_value())
|
||||
# Clamp to 32-bit signed integer range to avoid overflow
|
||||
max_int32 = 2147483647 # 2^31 - 1
|
||||
min_int32 = -2147483648 # -2^31
|
||||
new_position = max(min_int32, min(max_int32, new_position))
|
||||
try:
|
||||
new_position = int(scale.get_value())
|
||||
# Clamp to 32-bit signed integer range to avoid overflow
|
||||
max_int32 = 2147483647 # 2^31 - 1
|
||||
min_int32 = -2147483648 # -2^31
|
||||
new_position = max(min_int32, min(max_int32, new_position))
|
||||
self.player.position = new_position
|
||||
self.position_label.set_label(self.length_str(new_position))
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
# If setting position fails, just update the label
|
||||
self.position_label.set_label(self.length_str(new_position))
|
||||
except Exception:
|
||||
# If setting position fails, just update the label
|
||||
self.position_label.set_label(self.length_str(new_position))
|
||||
try:
|
||||
self.position_label.set_label(self.length_str(new_position))
|
||||
except Exception:
|
||||
logger.warning(f"Failed to update position label: {e}")
|
||||
|
||||
@cooldown(0.1)
|
||||
def _on_scale_move(self, scale: Scale, event, pos: int):
|
||||
self.player.position = pos
|
||||
self.position_label.set_label(self.length_str(pos))
|
||||
self.seek_bar.set_value(pos)
|
||||
try:
|
||||
if not self.exit and self.player:
|
||||
self.player.position = pos
|
||||
self.position_label.set_label(self.length_str(pos))
|
||||
self.seek_bar.set_value(pos)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to update seek position: {e}")
|
||||
|
||||
|
||||
class Thing(Box):
|
||||
|
||||
@@ -288,16 +288,26 @@ class ModusControlCenter(Window):
|
||||
Box(
|
||||
orientation="horizontal",
|
||||
spacing=8,
|
||||
v_expand=False,
|
||||
v_expand=True,
|
||||
h_expand=True,
|
||||
children=[
|
||||
self.volume_scale,
|
||||
Button(
|
||||
name="per-app-volume-button",
|
||||
child=Svg(
|
||||
svg_file=get_relative_path(
|
||||
"../../config/assets/icons/player/audio-switcher.svg"
|
||||
),
|
||||
name="per-app-volume-icon",
|
||||
child=Box(
|
||||
h_align="center",
|
||||
v_align="center",
|
||||
h_expand=True,
|
||||
size=(32, 32),
|
||||
v_expand=True,
|
||||
children=[
|
||||
Svg(
|
||||
svg_file=get_relative_path(
|
||||
"../../config/assets/icons/player/audio-switcher.svg"
|
||||
),
|
||||
size=34,
|
||||
)
|
||||
],
|
||||
),
|
||||
on_clicked=self.open_per_app_volume,
|
||||
),
|
||||
|
||||
@@ -16,6 +16,7 @@ from fabric.widgets.image import Image
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.widgets.overlay import Overlay
|
||||
from fabric.widgets.stack import Stack
|
||||
from fabric.widgets.svg import Svg
|
||||
from gi.repository import GLib, GObject
|
||||
from loguru import logger
|
||||
|
||||
@@ -84,7 +85,7 @@ class PlayerBoxStack(Box):
|
||||
|
||||
self.mpris_manager = mpris_manager
|
||||
|
||||
# Track connections for cleanup
|
||||
# Track connections for cleanup - store (object, handler_id) tuples
|
||||
connections = bulk_connect(
|
||||
self.mpris_manager,
|
||||
{
|
||||
@@ -92,7 +93,9 @@ class PlayerBoxStack(Box):
|
||||
"player-vanished": self.on_lost_player,
|
||||
},
|
||||
)
|
||||
self._signal_connections.extend(connections)
|
||||
# Store as (object, handler_id) tuples
|
||||
for handler_id in connections:
|
||||
self._signal_connections.append((self.mpris_manager, handler_id))
|
||||
|
||||
for player in self.mpris_manager.players: # type: ignore
|
||||
logger.info(
|
||||
@@ -103,9 +106,9 @@ class PlayerBoxStack(Box):
|
||||
def destroy(self):
|
||||
"""Clean up resources when the widget is destroyed."""
|
||||
# Disconnect all signal connections
|
||||
for connection in self._signal_connections:
|
||||
for obj, handler_id in self._signal_connections:
|
||||
try:
|
||||
connection.disconnect()
|
||||
obj.disconnect(handler_id)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to disconnect signal: {e}")
|
||||
self._signal_connections.clear()
|
||||
@@ -567,18 +570,15 @@ class PlayerBox(Box):
|
||||
)
|
||||
|
||||
# Create SVG icons with consistent sizing
|
||||
self.skip_next_icon = Image(
|
||||
image_file=get_relative_path("../../config/assets/icons/player/fwd.svg"),
|
||||
icon_size=16
|
||||
self.skip_next_icon = Svg(
|
||||
name="control-buttons",
|
||||
size=(28, 28),
|
||||
svg_file=get_relative_path("../../config/assets/icons/player/fwd.svg"),
|
||||
)
|
||||
self.skip_prev_icon = Image(
|
||||
image_file=get_relative_path("../../config/assets/icons/player/Rewind.svg"),
|
||||
icon_size=16
|
||||
)
|
||||
self.shuffle_icon = Image(icon_name="shuffle", icon_size=16)
|
||||
self.play_pause_icon = Image(
|
||||
image_file=get_relative_path("../../config/assets/icons/player/Pause.svg"),
|
||||
icon_size=18
|
||||
self.play_pause_icon = Svg(
|
||||
name="control-buttons",
|
||||
size=(28, 28),
|
||||
svg_file=get_relative_path("../../config/assets/icons/player/Pause.svg"),
|
||||
)
|
||||
|
||||
# Fixed size buttons to prevent layout shifts
|
||||
@@ -588,39 +588,19 @@ class PlayerBox(Box):
|
||||
on_clicked=self.player.play_pause,
|
||||
)
|
||||
# Set consistent button size
|
||||
self.play_pause_button.set_size_request(32, 32)
|
||||
|
||||
self.player.bind_property("can_pause", self.play_pause_button, "sensitive")
|
||||
|
||||
self.next_button = Button(
|
||||
style_classes=["player-button"],
|
||||
name="player-button",
|
||||
child=self.skip_next_icon,
|
||||
on_clicked=self._on_player_next,
|
||||
)
|
||||
# Set consistent button size
|
||||
self.next_button.set_size_request(32, 32)
|
||||
# self.next_button.set_size_request(32, 32)
|
||||
self.player.bind_property("can_go_next", self.next_button, "sensitive")
|
||||
|
||||
self.prev_button = Button(
|
||||
style_classes=["player-button"],
|
||||
child=self.skip_prev_icon,
|
||||
on_clicked=self._on_prev_button_click,
|
||||
)
|
||||
# Set consistent button size
|
||||
self.prev_button.set_size_request(32, 32)
|
||||
|
||||
self.shuffle_button = Button(
|
||||
style_classes=["player-button"],
|
||||
child=self.shuffle_icon,
|
||||
on_clicked=self.player.toggle_shuffle,
|
||||
)
|
||||
# Set consistent button size
|
||||
self.shuffle_button.set_size_request(32, 32)
|
||||
self.player.bind_property("can_shuffle", self.shuffle_button, "sensitive")
|
||||
|
||||
self.button_box.children = (
|
||||
# self.shuffle_button,
|
||||
# self.prev_button,
|
||||
self.play_pause_button,
|
||||
self.next_button,
|
||||
)
|
||||
@@ -682,7 +662,7 @@ class PlayerBox(Box):
|
||||
self.box,
|
||||
]
|
||||
|
||||
# Track signal connections for cleanup
|
||||
# Track signal connections for cleanup - store (object, handler_id) tuples
|
||||
connections = bulk_connect(
|
||||
self.player,
|
||||
{
|
||||
@@ -692,7 +672,9 @@ class PlayerBox(Box):
|
||||
"notify::metadata": self._on_metadata,
|
||||
},
|
||||
)
|
||||
self._signal_connections.extend(connections)
|
||||
# Store as (object, handler_id) tuples
|
||||
for handler_id in connections:
|
||||
self._signal_connections.append((self.player, handler_id))
|
||||
|
||||
def destroy(self):
|
||||
"""Clean up all resources when the widget is destroyed."""
|
||||
@@ -700,9 +682,9 @@ class PlayerBox(Box):
|
||||
self._download_cancelled = True
|
||||
|
||||
# Disconnect all signal connections
|
||||
for connection in self._signal_connections:
|
||||
for obj, handler_id in self._signal_connections:
|
||||
try:
|
||||
connection.disconnect()
|
||||
obj.disconnect(handler_id)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to disconnect signal: {e}")
|
||||
self._signal_connections.clear()
|
||||
@@ -782,24 +764,6 @@ class PlayerBox(Box):
|
||||
self.shuffle_icon.style_classes = []
|
||||
self.shuffle_icon.add_style_class("shuffle-off")
|
||||
|
||||
def play_pause(self, player, status):
|
||||
status = player.get_property("playback-status")
|
||||
|
||||
if status == "paused":
|
||||
self.play_pause_icon.set_from_file(
|
||||
get_relative_path("../../config/assets/icons/player/play.svg")
|
||||
)
|
||||
|
||||
if status == "playing":
|
||||
self.play_pause_icon.set_from_file(
|
||||
get_relative_path("../../config/assets/icons/player/Pause.svg")
|
||||
)
|
||||
# Notify the player stack that this player started playing
|
||||
if self.player_stack and hasattr(
|
||||
self.player_stack, "on_player_playback_changed"
|
||||
):
|
||||
self.player_stack.on_player_playback_changed(self, status)
|
||||
|
||||
def _on_playback_change(self, player, status):
|
||||
status = player.get_property("playback-status")
|
||||
|
||||
|
||||
Reference in new issue
Block a user