Enchancements

This commit is contained in:
tr1x_em committed 2025-08-06 22:56:54 +05:30
1 parent e44d2aef00
commit 8c56d9b236
6 files changed
+131 -120

No files matched your search

+7 -3
View File
@@ -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

+70 -39
View File
@@ -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):
+16 -6
View File
@@ -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,
),
+23 -59
View File
@@ -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")
+5 -6
View File
@@ -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
View File
@@ -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;