387 lines
13 KiB
QML
387 lines
13 KiB
QML
// --- 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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|