import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Services.Mpris import Quickshell.Wayland Item { id: root property bool open: false property var selectedPlayer: null property var player: selectedPlayer || Mpris.players.values.find(candidate => candidate.playbackState === MprisPlaybackState.Playing) || Mpris.players.values[0] || null signal dismissed() function formatTime(value) { if (!value || value < 0) return "0:00"; const seconds = Math.floor(value); return Math.floor(seconds / 60) + ":" + String(seconds % 60).padStart(2, "0"); } PanelWindow { visible: root.open color: "transparent" anchors { top: true bottom: true left: true right: true } exclusionMode: ExclusionMode.Ignore WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.keyboardFocus: root.open ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None WlrLayershell.namespace: "quickshell-media-popup" MouseArea { anchors.fill: parent onClicked: root.dismissed() } Rectangle { id: card width: Math.min(410, parent.width - 32) height: root.player ? 330 : 150 anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter anchors.topMargin: 52 radius: 20 color: "#f5f2ea" border.color: "#d8d0c2" border.width: 1 z: 1 MouseArea { anchors.fill: parent onClicked: function(mouse) { mouse.accepted = true; } } ColumnLayout { anchors.fill: parent anchors.margins: 16 spacing: 10 RowLayout { Layout.fillWidth: true Label { text: "Media" color: "#252321" font.pixelSize: 20 font.bold: true Layout.fillWidth: true } Label { text: root.player ? root.player.identity : "No player" color: "#7c756c" elide: Text.ElideRight } Rectangle { width: 26 height: 26 radius: 13 color: "#e5e0d7" Label { anchors.centerIn: parent text: "x" color: "#6e675f" } MouseArea { anchors.fill: parent onClicked: root.dismissed() } } } RowLayout { visible: root.player !== null Layout.fillWidth: true Layout.fillHeight: true spacing: 14 Rectangle { Layout.preferredWidth: 132 Layout.preferredHeight: 132 radius: 14 color: "#e5e0d7" clip: true Image { id: cover anchors.fill: parent source: root.player ? root.player.trackArtUrl : "" fillMode: Image.PreserveAspectCrop asynchronous: true } Label { anchors.centerIn: parent visible: cover.status !== Image.Ready text: "♪" color: "#8b8176" font.pixelSize: 42 } } ColumnLayout { Layout.fillWidth: true spacing: 4 Label { text: root.player ? (root.player.trackTitle || "Unknown track") : "" color: "#252321" font.pixelSize: 17 font.bold: true elide: Text.ElideRight Layout.fillWidth: true } Label { text: root.player ? (root.player.trackArtist || root.player.trackAlbum || "Unknown artist") : "" color: "#7c756c" elide: Text.ElideRight Layout.fillWidth: true } Item { Layout.fillHeight: true } Label { text: root.player ? root.formatTime(root.player.position) + " / " + root.formatTime(root.player.length) : "" color: "#7c756c" font.pixelSize: 11 } } } Slider { visible: root.player && root.player.length > 0 Layout.fillWidth: true from: 0 to: root.player ? root.player.length : 1 value: root.player ? root.player.position : 0 onMoved: { if (root.player && root.player.canSeek) root.player.position = value; } } RowLayout { visible: root.player !== null Layout.alignment: Qt.AlignHCenter spacing: 14 Button { text: "Prev" enabled: !!(root.player && root.player.canGoPrevious) onClicked: root.player.previous() } Button { text: root.player && root.player.playbackState === MprisPlaybackState.Playing ? "Pause" : "Play" enabled: !!(root.player && root.player.canTogglePlaying) onClicked: root.player.togglePlaying() } Button { text: "Next" enabled: !!(root.player && root.player.canGoNext) onClicked: root.player.next() } } RowLayout { visible: Mpris.players.values.length > 1 Layout.fillWidth: true Label { text: "Players" color: "#7c756c" Layout.fillWidth: true } Repeater { model: Mpris.players delegate: Button { required property var modelData text: modelData.identity onClicked: root.selectedPlayer = modelData } } } } } } }