// --- This file has been auto-generated. For permanent changes alter the appropriate block in the README.org. --- import Quickshell import Quickshell.Io import QtQuick import QtQuick.Layouts ShellRoot { QtObject { id: colors readonly property color baseAlpha: Qt.rgba(30/255, 30/255, 46/255, 0.95) readonly property color base: "#1e1e2e" readonly property color surface0: "#313244" readonly property color surface1: "#45475a" readonly property color surface2: "#585b70" readonly property color text: "#cdd6f4" readonly property color subtext0: "#a6adc8" readonly property color subtext1: "#bac2de" readonly property color blue: "#89b4fa" readonly property color green: "#a6e3a1" readonly property color teal: "#94e2d5" readonly property color red: "#f38ba8" readonly property color mauve: "#cba6f7" readonly property color peach: "#fab387" readonly property color lavender: "#b4befe" } QtObject { id: media property string artist: "" property string title: "" property string album: "" property string artUrl: "" property string status: "" property string device: "" property string player: "" property real progress: 0.0 property real duration: 0.0 property real position: 0.0 property int shuffleMode: 0 readonly property bool isSpotify: player.indexOf("spotify") !== -1 } Timer { interval: 1000 running: true repeat: true onTriggered: { playerProc.running = true artistProc.running = true titleProc.running = true albumProc.running = true artProc.running = true statusProc.running = true positionProc.running = true lengthProc.running = true if (media.isSpotify) shuffleProc.running = true } } Process { id: playerProc command: ["playerctl", "-l"] stdout: StdioCollector { onStreamFinished: { var lines = text.trim().split("\n") for (var i = 0; i < lines.length; i++) { if (lines[i].indexOf("spotify") !== -1) { media.player = lines[i].trim() return } } media.player = lines[0] ? lines[0].trim() : "" } } } Process { id: artistProc command: ["playerctl", "metadata", "artist"] stdout: StdioCollector { onStreamFinished: media.artist = text.trim() } } Process { id: titleProc command: ["playerctl", "metadata", "title"] stdout: StdioCollector { onStreamFinished: media.title = text.trim() } } Process { id: albumProc command: ["playerctl", "metadata", "album"] stdout: StdioCollector { onStreamFinished: media.album = text.trim() } } Process { id: artProc command: ["playerctl", "metadata", "mpris:artUrl"] stdout: StdioCollector { onStreamFinished: media.artUrl = text.trim() } } Process { id: statusProc command: ["playerctl", "status"] stdout: StdioCollector { onStreamFinished: media.status = text.trim() } } Process { id: positionProc command: ["playerctl", "position"] stdout: StdioCollector { onStreamFinished: { media.position = parseFloat(text.trim()) || 0 if (media.duration > 0) media.progress = media.position / media.duration } } } Process { id: lengthProc command: ["playerctl", "metadata", "mpris:length"] stdout: StdioCollector { onStreamFinished: { media.duration = (parseFloat(text.trim()) || 0) / 1000000 } } } Process { id: shuffleProc command: ["playerctl", "--player=" + media.player, "shuffle"] stdout: StdioCollector { onStreamFinished: { media.shuffleMode = (text.trim() === "On") ? 1 : 0 } } } Process { id: shuffleOnProc; command: ["playerctl", "--player=" + media.player, "shuffle", "on"] } Process { id: shuffleOffProc; command: ["playerctl", "--player=" + media.player, "shuffle", "off"] } function cycleShuffleMode() { var next = (media.shuffleMode + 1) % 2 media.shuffleMode = next if (next === 0) shuffleOffProc.running = true else shuffleOnProc.running = true } // Focus spotify — uses exact lowercase class as reported by hyprctl Process { id: focusSpotifyProc command: ["hyprctl", "dispatch", "focuswindow", "class:^(spotify)$"] } function focusPlayer() { if (media.isSpotify) { focusSpotifyProc.running = true } } Process { id: prevProc; command: ["playerctl", "previous"] } Process { id: playProc; command: ["playerctl", "play-pause"] } Process { id: nextProc; command: ["playerctl", "next"] } FloatingWindow { id: root title: "quickshell-media" visible: true implicitWidth: 300 implicitHeight: 420 color: "transparent" Shortcut { sequence: "Escape" onActivated: Qt.quit() } // Gradient border — hidden when app has focus Rectangle { anchors.fill: parent anchors.margins: -2 radius: 18 z: -1 opacity: Qt.application.active ? 0 : 1 Behavior on opacity { NumberAnimation { duration: 150 } } gradient: Gradient { orientation: Gradient.Horizontal GradientStop { position: 0.0; color: colors.blue } GradientStop { position: 1.0; color: colors.green } } } Rectangle { anchors.fill: parent radius: 16 color: colors.base ColumnLayout { anchors { fill: parent margins: 16 } spacing: 12 // Album art — click to focus player Rectangle { Layout.fillWidth: true Layout.preferredHeight: 200 radius: 12 color: colors.surface0 clip: true Image { anchors.fill: parent source: media.artUrl fillMode: Image.PreserveAspectCrop visible: media.artUrl !== "" } Text { anchors.centerIn: parent text: "󰝚" font.pixelSize: 48 color: colors.surface2 visible: media.artUrl === "" } MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: focusPlayer() } } // Artist Text { Layout.fillWidth: true text: media.artist || "Unknown artist" color: colors.subtext1 font.pixelSize: 12 elide: Text.ElideRight } // Title Text { Layout.fillWidth: true text: media.title || "Nothing playing" color: colors.text font.pixelSize: 14 font.bold: true elide: Text.ElideRight } // Album Text { Layout.fillWidth: true text: media.album color: colors.subtext0 font.pixelSize: 11 elide: Text.ElideRight visible: media.album !== "" } // Device (Spotify) Text { Layout.fillWidth: true text: "󰓻 " + media.device color: colors.green font.pixelSize: 11 visible: media.device !== "" } // Progress bar Rectangle { Layout.fillWidth: true height: 4 radius: 2 color: colors.surface1 Rectangle { width: parent.width * media.progress height: parent.height radius: parent.radius gradient: Gradient { orientation: Gradient.Horizontal GradientStop { position: 0.0; color: colors.blue } GradientStop { position: 1.0; color: colors.green } } } } // Time RowLayout { Layout.fillWidth: true Text { text: { var m = Math.floor(media.position / 60) var s = Math.floor(media.position % 60) return m + ":" + (s < 10 ? "0" + s : s) } color: colors.subtext0 font.pixelSize: 11 } Item { Layout.fillWidth: true } Text { text: { var m = Math.floor(media.duration / 60) var s = Math.floor(media.duration % 60) return m + ":" + (s < 10 ? "0" + s : s) } color: colors.subtext0 font.pixelSize: 11 } } // Playback controls + shuffle RowLayout { Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter spacing: 20 // Shuffle button (Spotify only) Item { visible: media.isSpotify width: 28 height: 28 Text { anchors.centerIn: parent text: "󰒟" font.pixelSize: 18 color: media.shuffleMode === 0 ? colors.surface2 : colors.blue } MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: cycleShuffleMode() } } Text { text: "󰒮" font.pixelSize: 22 color: prevHover.containsMouse ? colors.blue : colors.text MouseArea { id: prevHover anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: prevProc.running = true } } Text { text: media.status === "Playing" ? "󰏤" : "󰐊" font.pixelSize: 28 color: playHover.containsMouse ? colors.green : colors.text MouseArea { id: playHover anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: playProc.running = true } } Text { text: "󰒭" font.pixelSize: 22 color: nextHover.containsMouse ? colors.blue : colors.text MouseArea { id: nextHover anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: nextProc.running = true } } } } } } }