pragma Singleton import QtQuick import Quickshell import Quickshell.Services.Mpris import Quickshell.Hyprland import "../.." Singleton { id: root; property MprisPlayer trackedPlayer: null; property MprisPlayer activePlayer: trackedPlayer ?? Mpris.players.values[0] ?? null; signal trackChanged(reverse: bool); property bool __reverse: false; property var activeTrack; Component.onCompleted: { for (const player of Mpris.players.values) { if (player.playbackState == MprisPlaybackState.Playing) { if (root.trackedPlayer == null) { root.trackedPlayer = player; } } player.playbackStateChanged.connect(() => { if (root.trackedPlayer !== player) root.trackedPlayer = player; }); } } Connections { target: activePlayer function onTrackChanged() { root.updateTrack(); } } // Change the tracked player when one changes playback state or is created in a playing state. Connections { target: Mpris.players; function onObjectInsertedPost(player: MprisPlayer) { if (player.playbackState === MprisPlaybackState.Playing) { if (root.trackedPlayer !== player) root.trackedPlayer = player; } player.playbackStateChanged.connect(() => { if (root.trackedPlayer !== player) root.trackedPlayer = player; }); } function onObjectRemovedPre() { console.log(`trackedPlayer: ${root.trackedPlayer}`) if (root.trackedPlayer == null) { for (const player of Mpris.players.values) { if (player.playbackState === MprisPlaybackState.Playing) { root.trackedPlayer = player; break; } } } } } onActivePlayerChanged: this.updateTrack(); function updateTrack() { const metadata = this.activePlayer?.metadata ?? {}; this.activeTrack = { artUrl: metadata["mpris:artUrl"] ?? "", title: metadata["xesam:title"] ?? "", artist: metadata["xesam:artist"] ?? "", }; this.trackChanged(__reverse); this.__reverse = false; } property bool isPlaying: this.activePlayer && this.activePlayer.playbackState == MprisPlaybackState.Playing; property bool canPlay: this.activePlayer?.canPlay ?? false; function play() { if (this.canPlay) this.activePlayer.playbackState = MprisPlaybackState.Playing; } property bool canPause: this.activePlayer?.canPause ?? false; function pause() { if (this.canPause) this.activePlayer.playbackState = MprisPlaybackState.Paused; } property bool canGoPrevious: this.activePlayer?.canGoPrevious ?? false; function previous() { if (this.canGoPrevious) { this.__reverse = true; this.activePlayer.previous(); } } property bool canGoNext: this.activePlayer?.canGoNext ?? false; function next() { if (this.canGoNext) { this.__reverse = false; this.activePlayer.next(); } } property bool canChangeVolume: this.activePlayer && this.activePlayer.volumeSupported && this.activePlayer.canControl; property bool loopSupported: this.activePlayer && this.activePlayer.loopSupported && this.activePlayer.canControl; property var loopState: this.activePlayer?.loopState ?? MprisLoopState.None; function setLoopState(loopState: var) { if (this.loopSupported) { this.activePlayer.loopState = loopState; } } property bool shuffleSupported: this.activePlayer && this.activePlayer.shuffleSupported && this.activePlayer.canControl; property bool hasShuffle: this.activePlayer?.shuffle ?? false; function setShuffle(shuffle: bool) { if (this.shuffleSupported) { this.activePlayer.shuffle = shuffle; } } function setActivePlayer(player: MprisPlayer) { const targetPlayer = player ?? MprisPlayer.players[0]; console.log(`setactive: ${targetPlayer} from ${activePlayer}`) if (targetPlayer && this.activePlayer) { this.__reverse = Mpris.players.indexOf(targetPlayer) < Mpris.players.indexOf(this.activePlayer); } else { // always animate forward if going to null this.__reverse = false; } this.trackedPlayer = targetPlayer; } Shortcut { name: "music-pauseall"; onPressed: { for (let i = 0; i < Mpris.players.length; i++) { const player = Mpris.players[i]; if (player.canPause) player.playbackState = MprisPlaybackState.Paused; } } } Shortcut { name: "music-playpause"; onPressed: { if (root.isPlaying) root.pause(); else root.play(); } } Shortcut { name: "music-previous"; onPressed: root.previous(); } Shortcut { name: "music-next"; onPressed: root.next(); } }