Enchancements
This commit is contained in:
1 parent
e44d2aef00
commit
8c56d9b236
6 files changed
+131
-120
No files matched your search
@@ -1,3 +1,7 @@
|
||||
<svg width="23" height="30" viewBox="0 0 23 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.92233 29.6592C1.37016 29.6592 0.549467 28.8385 0.549467 27.2685V2.96901C0.549467 1.41684 1.37016 0.596152 2.92233 0.596152H6.99008C8.54226 0.596152 9.36294 1.34548 9.36294 2.96901V27.2685C9.36294 28.8385 8.54226 29.6592 6.99008 29.6592H2.92233ZM15.8928 29.6592C14.3228 29.6592 13.5021 28.8385 13.5021 27.2685V2.96901C13.5021 1.41684 14.3228 0.596152 15.8928 0.596152H19.9427C21.5127 0.596152 22.3155 1.34548 22.3155 2.96901V27.2685C22.3155 28.8385 21.5127 29.6592 19.9427 29.6592H15.8928Z" fill="white"/>
|
||||
</svg>
|
||||
<svg width="47" height="47" xmlns="http://www.w3.org/2000/svg" fill="none">
|
||||
|
||||
<g>
|
||||
<title>Layer 1</title>
|
||||
<path id="svg_1" fill="white" d="m14.98985,38.03152c-1.55217,0 -2.37286,-0.8207 -2.37286,-2.3907l0,-24.29949c0,-1.55217 0.82069,-2.37286 2.37286,-2.37286l4.06775,0c1.55218,0 2.37286,0.74933 2.37286,2.37286l0,24.29949c0,1.57 -0.82068,2.3907 -2.37286,2.3907l-4.06775,0zm12.97047,0c-1.57,0 -2.3907,-0.8207 -2.3907,-2.3907l0,-24.29949c0,-1.55217 0.8207,-2.37286 2.3907,-2.37286l4.0499,0c1.57,0 2.3728,0.74933 2.3728,2.37286l0,24.29949c0,1.57 -0.8028,2.3907 -2.3728,2.3907l-4.0499,0z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 620 B After Width: | Height: | Size: 603 B |
@@ -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")
|
||||
|
||||
|
||||
@@ -474,9 +474,8 @@
|
||||
/* Circle button for per-app volume */
|
||||
#per-app-volume-button {
|
||||
background-color: alpha(#fff, 0.15);
|
||||
border: 2px solid alpha(#999, 0.6);
|
||||
border: 1px solid alpha(#999, 0.6);
|
||||
border-radius: 50%;
|
||||
min-width: 30px;
|
||||
}
|
||||
|
||||
#per-app-volume-button:hover {
|
||||
@@ -678,6 +677,7 @@
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
min-width: 28px; /* Slightly smaller */
|
||||
opacity: 0.7;
|
||||
min-height: 28px;
|
||||
padding: 5px; /* Reduced padding */
|
||||
color: #ffffff;
|
||||
@@ -685,8 +685,7 @@
|
||||
}
|
||||
|
||||
#macos-control-button:hover {
|
||||
background: alpha(#fff, 0.1);
|
||||
box-shadow: 0 0 0 1px alpha(#fff, 0.2);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#macos-play-button {
|
||||
@@ -694,6 +693,7 @@
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
min-width: 36px; /* Slightly smaller */
|
||||
opacity: 0.7;
|
||||
min-height: 36px;
|
||||
padding: 7px; /* Reduced padding */
|
||||
color: #ffffff;
|
||||
@@ -701,8 +701,7 @@
|
||||
}
|
||||
|
||||
#macos-play-button:hover {
|
||||
background: alpha(#fff, 0.15);
|
||||
box-shadow: 0 0 0 1px alpha(#fff, 0.25);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* macOS player switcher dots - compact horizontal layout */
|
||||
|
||||
+10
-7
@@ -103,20 +103,23 @@
|
||||
}
|
||||
|
||||
.player-button {
|
||||
min-width: 20px;
|
||||
min-height: 20px;
|
||||
padding: 1px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.player-button:hover {
|
||||
box-shadow: inset rgba(255, 255, 255, 0.5) 0 0 10px;
|
||||
}
|
||||
|
||||
#player-box #player-controls #button-box .player-button:disabled {
|
||||
color: #5f5f5f;
|
||||
}
|
||||
|
||||
#player-button {
|
||||
opacity: 0.5;
|
||||
}
|
||||
#player-button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
#btn svg {
|
||||
margin: 0 2px;
|
||||
}
|
||||
.album-image-c {
|
||||
min-width: 50px;
|
||||
min-height: 50px;
|
||||
|
||||
Reference in new issue
Block a user