Skip to content

Commit 2024e7b

Browse files
committed
Add multi-player support with rofi selection menu
- Support multiple media players (Firefox, Chrome, Chromium, Thorium, Spotify) - Add right-click menu to select active player using rofi - Fix scroll up/down for next/previous track controls - Fix play-pause functionality using playerctl CLI - Player selection persists in /tmp/waybar-mediaplayer-selected-player
1 parent 19f10c0 commit 2024e7b

File tree

2 files changed

+131
-29
lines changed

2 files changed

+131
-29
lines changed

src/config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"text_rot_int": 1000,
1111
"image_signal": 4,
1212
"length_factor": 1,
13-
"player_name": "spotify",
13+
"player_name": ["firefox", "chrome", "chromium", "thorium", "spotify"],
1414
"convert_to_jpeg": false,
1515
"album_art_placeholder": "no",
1616
"lyrics_providers": ["Lrclib", "Musixmatch", "NetEase", "Megalobiz"],

src/mediaplayer

Lines changed: 130 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ from gi.repository import GLib, Playerctl # noqa: E402
3030
config = None
3131
logger = logging.getLogger(__name__)
3232
artfp = Path("/tmp/waybar-mediaplayer-art")
33+
selected_player_fp = Path("/tmp/waybar-mediaplayer-selected-player")
34+
current_player_name = None
3335
last_metadata = None
3436
last_art_url = None
3537
last_rot = 0
@@ -43,19 +45,58 @@ is_text_rotating = None
4345
lyrics = dict()
4446

4547

46-
def on_player_appeared(manager, player, requested_player):
48+
def on_player_appeared(manager, player, requested_players):
4749
logger.debug("I was called")
48-
if player and (player.name == requested_player):
50+
if player and any(player.name.startswith(rp) for rp in requested_players):
4951
init_player(manager, player)
5052
else:
5153
logger.debug(
5254
"New player appeared, but it's not the selected player, skipping"
5355
)
54-
5556
logger.debug("Returning True")
5657
return True
5758

5859

60+
def get_selected_player():
61+
"""Get the currently selected player from file."""
62+
if selected_player_fp.is_file():
63+
with open(selected_player_fp, "r") as f:
64+
return f.read().strip()
65+
return None
66+
67+
68+
def set_selected_player(player_name):
69+
"""Save the selected player to file."""
70+
with open(selected_player_fp, "w") as f:
71+
f.write(player_name)
72+
73+
74+
def get_active_player(manager, requested_players):
75+
"""Get the active player, prioritizing the selected one."""
76+
selected = get_selected_player()
77+
available_players = []
78+
79+
result = subprocess.run(
80+
["playerctl", "--list-all"],
81+
capture_output=True,
82+
text=True,
83+
timeout=1
84+
)
85+
if result.returncode == 0:
86+
all_players = result.stdout.strip().split('\n')
87+
for player in all_players:
88+
if player and any(player.startswith(rp) for rp in requested_players):
89+
available_players.append(player)
90+
91+
if not available_players:
92+
return None
93+
94+
if selected and selected in available_players:
95+
return selected
96+
97+
return available_players[0]
98+
99+
59100
def delete_album_art():
60101
p = Path(__file__)
61102
if config["album_art_placeholder"] == "dark":
@@ -95,7 +136,6 @@ def on_metadata(player, metadata, manager):
95136
def on_playback_status(player, status, manager):
96137
logger.info("I was called")
97138
logger.debug(f"status={status}")
98-
# Update the icon in the progressbar (play/pause)
99139
logger.debug("Calling register_refresh_interval_callback")
100140
register_refresh_interval_callback(player, manager)
101141
logger.debug("Calling update_progressbar")
@@ -107,19 +147,16 @@ def on_playback_status(player, status, manager):
107147
def on_refresh_interval(manager):
108148
global is_refint
109149
logger.debug("I was called")
110-
# If there are no players, stop calling this handler
111150
if len(manager.props.players) == 0:
112151
logger.debug("Returning False")
113152
is_refint = False
114153
return False
115154
player = manager.props.players[0]
116-
# If we are not playing, stop calling this handler
117155
is_playing = player.props.status == "Playing"
118156
if not is_playing:
119157
logger.debug("Returning False")
120158
is_refint = False
121159
return False
122-
# Update progressbar and return
123160
update_progressbar(manager, player)
124161
logger.debug("Returning True")
125162
return True
@@ -525,8 +562,11 @@ def update_progressbar(manager, player):
525562

526563

527564
def init_player(manager, name):
565+
global current_player_name
528566
logger.debug("I was called")
529567
logger.debug(f"name.name={name.name}")
568+
# Track current player
569+
current_player_name = name.name
530570
# Register handlers
531571
player = Playerctl.Player.new_from_name(name)
532572
player.connect("playback-status", on_playback_status, manager)
@@ -604,33 +644,95 @@ def main():
604644

605645
manager = Playerctl.PlayerManager()
606646

607-
requested_player = config["player_name"].split(".")[0]
647+
# Support both string and list for player_name
648+
player_names = config["player_name"]
649+
if isinstance(player_names, str):
650+
player_names = [player_names]
651+
# Split on "." to get base player names
652+
requested_players = [pn.split(".")[0] for pn in player_names]
608653
logger.debug(
609-
f"Splitting player_name on `.`. requested_player={requested_player}"
654+
f"Requested players: {requested_players}"
610655
)
611656

612-
ini = False
613-
for player in manager.props.player_names:
614-
logger.debug(f"Found player '{player.name}'")
615-
if not player.name.startswith(requested_player):
616-
logger.debug("This is not the filtered player, skipping it")
617-
continue
618-
if arguments.command == "play-pause":
619-
Playerctl.Player.new(player.name).play_pause()
620-
sys.exit(0)
621-
elif arguments.command == "next":
622-
Playerctl.Player.new(player.name).next()
657+
# Handle select command
658+
if arguments.command == "select":
659+
# Get available players
660+
available = []
661+
for player in manager.props.player_names:
662+
if any(player.name.startswith(rp) for rp in requested_players):
663+
available.append(player.name)
664+
665+
if not available:
666+
print("No media players found")
623667
sys.exit(0)
624-
elif arguments.command == "previous":
625-
Playerctl.Player.new(player.name).previous()
668+
669+
# Use rofi for player selection
670+
menu_cmd = ["rofi", "-dmenu", "-p", "Select player:"]
671+
672+
# Show menu
673+
try:
674+
result = subprocess.run(
675+
menu_cmd,
676+
input="\n".join(available),
677+
capture_output=True,
678+
text=True
679+
)
680+
if result.returncode == 0 and result.stdout.strip():
681+
selected = result.stdout.strip()
682+
set_selected_player(selected)
683+
print(f"Selected player: {selected}")
684+
except Exception as e:
685+
logger.error(f"Error running menu: {e}")
686+
sys.exit(0)
687+
688+
# Handle control commands using playerctl CLI (more reliable for quick actions)
689+
if arguments.command in ["play-pause", "next", "previous"]:
690+
# Get active player
691+
active_player = get_active_player(manager, requested_players)
692+
693+
if not active_player:
694+
logger.debug("No active player found")
626695
sys.exit(0)
627-
elif arguments.command == "monitor":
696+
697+
# Use playerctl CLI for the action
698+
cmd_map = {
699+
"play-pause": "play-pause",
700+
"next": "next",
701+
"previous": "previous"
702+
}
703+
704+
cmd = ["playerctl", "--player=" + active_player, cmd_map[arguments.command]]
705+
logger.debug(f"Running command: {' '.join(cmd)}")
706+
subprocess.run(cmd)
707+
sys.exit(0)
708+
709+
# Handle monitor command
710+
ini = False
711+
if arguments.command == "monitor":
712+
# Prioritize selected player
713+
selected = get_selected_player()
714+
player_to_init = None
715+
716+
for player in manager.props.player_names:
717+
logger.debug(f"Found player '{player.name}'")
718+
if not any(player.name.startswith(rp) for rp in requested_players):
719+
logger.debug("This is not the filtered player, skipping it")
720+
continue
721+
722+
# Use selected player if available, otherwise first match
723+
if selected and player.name == selected:
724+
player_to_init = player
725+
break
726+
elif not player_to_init:
727+
player_to_init = player
728+
729+
if player_to_init:
628730
logger.debug("Initializing player")
629-
init_player(manager, player)
731+
init_player(manager, player_to_init)
630732
ini = True
631-
else:
632-
logger.critical(f"Invalid argument {sys.argv[1]}")
633-
sys.exit(1)
733+
elif arguments.command not in ["play-pause", "next", "previous", "select"]:
734+
logger.critical(f"Invalid argument {arguments.command}")
735+
sys.exit(1)
634736

635737
if not ini:
636738
logger.debug("No player found. Printing empty")
@@ -639,7 +741,7 @@ def main():
639741
loop = GLib.MainLoop()
640742

641743
on_player_appeared_inst = partial(
642-
on_player_appeared, requested_player=requested_player
744+
on_player_appeared, requested_players=requested_players
643745
)
644746
# manager.connect("player-appeared", on_player_appeared)
645747
manager.connect("name-appeared", on_player_appeared_inst)

0 commit comments

Comments
 (0)