Files
Modus/modules/dashboard/player_mini.py
T

467 lines
16 KiB
Python

from gi.repository import Gdk
from fabric.widgets.box import Box
from fabric.widgets.label import Label
from fabric.widgets.centerbox import CenterBox
from fabric.widgets.button import Button
from fabric.widgets.stack import Stack
from fabric.widgets.eventbox import EventBox
from services.mpris import PlayerManager, PlayerService
from utils.wiggle_bar import WigglyWidget
import utils.icons as icons
import config.data as data
from loguru import logger
import gi
gi.require_version("Gtk", "3.0")
class PlayerMini(Box):
def __init__(self, player, **kwargs):
super().__init__(style_classes="player-mini", **kwargs)
if not data.VERTICAL:
self.remove_style_class(
"vertical"
) # vertical class binding from unknown source
self._player = PlayerService(player=player)
self.duration = 0.0
self._player.connect("pause", self.on_pause)
self._player.connect("play", self.on_play)
self._player.connect("meta-change", self.on_metadata)
self._player.connect("shuffle-toggle", self.on_shuffle)
self._player.connect("track-position", self.on_update_track_position)
self.player_name = Label(
name=player.props.player_name,
style_classes=["player-icon", "mini"],
markup=getattr(icons, player.props.player_name, icons.disc),
)
self.set_style(f"background-image:url('{data.HOME_DIR}/.current.wall')")
self.song = Label(
name="song",
label="song",
style_classes="mini",
justification="left",
h_align="start",
ellipsization="end",
max_chars_width=15,
)
self.artist = Label(
name="artist",
label="artist",
style_classes="mini",
justification="left",
h_align="start",
)
self.music = Box(
name="music",
h_expand=True,
v_expand=True,
children=[self.song],
)
self.play_pause_button = Button(
name="play-pause-button",
child=Label(name="play-pause-label", markup=icons.play),
style_classes="mini",
tooltip_text="Play/Pause",
on_clicked=lambda b, *_: self.handle_play_pause(player),
)
self.shuffle_button = Button(
name="shuffle-button",
child=Label(name="shuffle", markup=icons.shuffle),
on_clicked=lambda b, *_: self.handle_shuffle(b, player),
)
self.wiggly = WigglyWidget()
self.wiggly.connect("on-seek", self.on_seek)
self.wiggly_bar = Box(
orientation="v",
h_expand=True,
v_expand=True,
h_align="fill",
v_align="fill",
children=self.wiggly,
)
self.album_cover = Box(style_classes="album-image")
self.album_cover.set_style(
f"background-image:url('{data.HOME_DIR}/.current.wall')"
)
self.children = [
self.album_cover,
Box(
name="source",
style_classes="mini",
v_align="end",
children=self.player_name,
),
# CenterBox(
# name="progress-container",
# h_expand=True,
# v_expand=True,
# orientation="v",
# center_children=[self.wiggly_bar],
# ),
Box(
orientation="v",
v_expand=True,
h_expand=True,
style="padding-left:10px;",
children=[
CenterBox(
name="details",
center_children=self.music,
),
Box(
name="controls",
style_classes="horizontal" if not data.VERTICAL else "vertical",
spacing=5,
h_expand=True,
children=[
CenterBox(
h_expand=True,
v_expand=True,
center_children=[
Button(
name="prev-button",
child=Label(
name="play-previous",
style_classes="mini",
markup=icons.previous_fill,
),
on_clicked=lambda b, *_: self.handle_prev(
player
),
),
self.play_pause_button,
Button(
name="next-button",
child=Label(
name="play-next",
style_classes="mini",
markup=icons.next_fill,
),
on_clicked=lambda b, *_: self.handle_next(
player
),
),
# self.shuffle_button,
],
)
],
),
],
),
]
self.on_metadata(self._player, metadata=player.props.metadata, player=player)
def on_update_track_position(self, sender, pos, dur):
if dur == 0:
return
self.duration = dur
self.wiggly.update_value_from_signal(pos / dur)
def on_seek(self, sender, ratio):
pos = ratio * self.duration # duration in seconds
print(f"Seeking to {pos:.2f}s")
self._player.set_position(int(pos))
def skip_forward(self, seconds=10):
self._player._player.seek(seconds * 1000000)
def skip_backward(self, seconds=10):
self._player._player.seek(-1 * seconds * 1000000)
def on_metadata(self, sender, metadata, player):
keys = metadata.keys()
if "xesam:artist" in keys and "xesam:title" in keys:
# _max_chars = 20 if not data.VERTICAL else 30
song_title = metadata["xesam:title"]
# if len(song_title) > _max_chars:
# song_title = song_title[: _max_chars - 1] + "…"
self.song.set_label(song_title)
artist_list = metadata["xesam:artist"]
artist_name = artist_list[0] if artist_list else "Unknown Artist"
# if len(artist_name) > _max_chars:
# artist_name = artist_name[: _max_chars - 1] + "…"
self.artist.set_label(artist_name)
if "mpris:artUrl" in keys:
self.set_style(f"background-image:url('{metadata['mpris:artUrl']}')")
self.album_cover.set_style(
f"background-image:url('{metadata['mpris:artUrl']}')"
)
if (
player.props.playback_status.value_name
== "PLAYERCTL_PLAYBACK_STATUS_PLAYING"
):
self.on_play(self._player)
if player.props.shuffle == True:
self.shuffle_button.get_child().set_markup(icons.disable_shuffle)
self.shuffle_button.get_child().set_name("disable-shuffle")
else:
self.shuffle_button.get_child().set_markup(icons.shuffle)
self.shuffle_button.get_child().set_name("shuffle")
def on_pause(self, sender):
self.play_pause_button.get_child().set_markup(icons.play)
self.wiggly.dragging = True
self.wiggly.update_amplitude(True)
self.wiggly.pause = True
# self.play_pause_button.add_style_class("pause-track")
def on_play(self, sender):
self.play_pause_button.get_child().set_markup(icons.pause)
self.wiggly.pause = False
self.wiggly.dragging = False
self.wiggly.update_amplitude(False)
# self.play_pause_button.remove_style_class("pause-track")
def on_shuffle(self, sender, player, status):
print("callback status", status)
if status == False:
self.shuffle_button.get_child().set_markup(icons.shuffle)
self.shuffle_button.get_child().set_name("shuffle")
else:
self.shuffle_button.get_child().set_markup(icons.disable_shuffle)
self.shuffle_button.get_child().set_name("disable-shuffle")
self.shuffle_button.get_child().set_style("color: white")
def handle_next(self, player):
self._player._player.next()
def handle_prev(self, player):
self._player._player.previous()
def handle_play_pause(self, player):
is_playing = (
self._player._player.props.playback_status.value_name
== "PLAYERCTL_PLAYBACK_STATUS_PLAYING"
)
def _set_play_ui():
self.play_pause_button.get_child().set_markup(icons.pause)
# self.play_pause_button.remove_style_class("pause-track")
# self.play_pause_button.get_child().set_name("pause-label")
def _set_pause_ui():
self.play_pause_button.get_child().set_markup(icons.play)
# self.play_pause_button.add_style_class("pause-track")
# self.play_pause_button.get_child().set_name("play-label")
if is_playing:
_set_pause_ui()
else:
_set_play_ui()
try:
self._player._player.play_pause()
except Exception as e:
# revert if signal failed
if is_playing:
_set_pause_ui()
else:
_set_play_ui()
logger.warning("Failed to toggle playback:", e)
def handle_shuffle(self, shuffle_button, player):
print("shuffle", player.props.shuffle)
if player.props.shuffle == False:
player.set_shuffle(True)
print("setting to true", player.props.player_name)
else:
player.set_shuffle(False)
shuffle_button.get_child().set_style("color: var(--outline)")
class PlacheholderMini(Box):
def __init__(self, **kwargs):
super().__init__(style_classes="player-mini", **kwargs)
self.set_style(f"background-image:url('{data.HOME_DIR}/.current.wall')")
self.album_cover = Box(style_classes="album-image")
self.album_cover.set_style(
f"background-image:url('{data.HOME_DIR}/.current.wall')"
)
self.player_name = Label(
style_classes=["player-icon", "mini"], markup=icons.disc
)
self.children = [
self.album_cover,
Box(
name="source",
style_classes="mini",
v_align="end",
children=self.player_name,
),
Box(
orientation="v",
v_expand=True,
h_expand=True,
style="padding-left:10px;",
v_align="center",
children=[
CenterBox(
name="details",
center_children=Label(
label="Nothing Playing", style="color:black;"
),
)
],
),
]
class PlayerContainerMini(Box):
def __init__(self, **kwargs):
super().__init__(name="player-container", style_classes="mini", **kwargs)
self.manager = PlayerManager()
self.manager.connect("new-player", self.new_player)
self.manager.connect("player-vanish", self.on_player_vanish)
self.placeholder = PlacheholderMini()
self.stack = Stack(
# name="player-container",
transition_type="crossfade",
transition_duration=100,
children=[self.placeholder],
)
self.player_stack = Stack(
name="player-stack",
transition_type="crossfade",
transition_duration=100,
children=[],
)
self.player_switch_container = CenterBox(
name="player-switch-container",
orientation="v",
style_classes=(
["horizontal-player", "mini"]
if not data.VERTICAL
else "vertical-player"
),
center_children=[],
)
self.mini_tile_icon = Label(
name="disc",
style_classes=["tile-icon"],
style="font-size:30px; margin:0px; padding:0px;",
markup=icons.disc,
justification="center",
)
self.mini_tile_view = CenterBox(
style_classes=["tile", "mini", "on"],
v_expand=False,
center_children=self.mini_tile_icon,
)
self.event_box = EventBox(
events=["scroll"],
child=Box(children=[self.stack, self.player_switch_container]),
)
self.children = self.event_box
self.players = []
self.manager.init_all_players()
self.event_box.connect("scroll-event", self.on_scroll)
self.stack.connect("notify::visible-child", self.on_visible_child_changed)
def new_player(self, manager, player):
print(player.props.player_name, "new player")
print(player)
new_player = PlayerMini(player=player)
new_player.wiggly_bar.queue_draw()
new_player.set_name(player.props.player_name)
self.players.append(new_player)
print("stacking mini", player.props.player_name)
self.stack.add_named(new_player, player.props.player_name)
if len(self.players) == 1:
self.stack.remove(self.placeholder)
self.player_switch_container.add_center(
Button(
name=player.props.player_name,
style_classes=["player-button", "mini"],
on_clicked=lambda b: self.switch_player(player.props.player_name, b),
)
)
self.update_player_list()
def switch_player(self, player_name, button):
self.stack.set_visible_child_name(player_name)
for btn in self.player_switch_container.center_children:
btn.remove_style_class("active")
button.add_style_class("active")
def on_player_vanish(self, manager, player):
for player_instance in self.players:
if player_instance.get_name() == player.props.player_name:
self.stack.remove(player_instance)
self.players.remove(player_instance)
for btn in self.player_switch_container.center_children:
if btn.get_name() == player_instance.get_name():
self.player_switch_container.remove_center(btn)
self.update_player_list()
break
if len(self.players) == 0:
self.stack.add_named(self.placeholder, "placeholder")
def update_player_list(self):
curr = self.stack.get_visible_child()
for btn in self.player_switch_container.center_children:
print(btn.get_name())
if btn.get_name() == curr.get_name():
btn.add_style_class("active")
else:
btn.remove_style_class("active")
def on_scroll(self, widget, event):
match event.direction:
case 0:
self.switch_relative_player(forward=False)
case 1:
self.switch_relative_player(forward=True)
def switch_relative_player(self, forward=True):
if not self.players:
return
current_player = self.stack.get_visible_child()
current_index = self.players.index(current_player)
next_index = (current_index + (1 if forward else -1)) % len(self.players)
next_player = self.players[next_index]
for btn in self.player_switch_container.center_children:
if btn.get_name() == next_player.get_name():
self.switch_player(next_player.get_name(), btn)
break
def on_visible_child_changed(self, *args):
curr_player = self.stack.get_visible_child().get_name()
self.mini_tile_icon.set_name(curr_player)
self.mini_tile_icon.set_markup(getattr(icons, curr_player, icons.disc))
def get_mini_view(self):
return self.mini_tile_view