diff --git a/quickshell/Modules/DankDash/MediaPlayerTab.qml b/quickshell/Modules/DankDash/MediaPlayerTab.qml index 76df2153..6a72bb9f 100644 --- a/quickshell/Modules/DankDash/MediaPlayerTab.qml +++ b/quickshell/Modules/DankDash/MediaPlayerTab.qml @@ -62,8 +62,6 @@ Item { readonly property real currentVolume: usePlayerVolume ? activePlayer.volume : (AudioService.sink?.audio?.volume ?? 0) property bool isSwitching: false - property string _lastArtUrl: "" - property string _bgArtSource: "" // Derived "no players" state: always correct, no timers. readonly property int _playerCount: allPlayers ? allPlayers.length : 0 @@ -88,28 +86,7 @@ Item { isSwitching = true; _switchHold = true; _switchHoldTimer.restart(); - if (activePlayer.trackArtUrl) - loadArtwork(activePlayer.trackArtUrl); - } - - property string activeTrackArtFile: "" - - function loadArtwork(url) { - if (!url) - return; - if (url.startsWith("http://") || url.startsWith("https://")) { - const filename = "/tmp/.dankshell/trackart_" + Date.now() + ".jpg"; - activeTrackArtFile = filename; - - cleanupProcess.command = ["sh", "-c", "mkdir -p /tmp/.dankshell && find /tmp/.dankshell -name 'trackart_*' ! -name '" + filename.split('/').pop() + "' -delete"]; - cleanupProcess.running = true; - - imageDownloader.command = ["curl", "-L", "-s", "--user-agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36", "-o", filename, url]; - imageDownloader.targetFile = filename; - imageDownloader.running = true; - return; - } - _bgArtSource = url; + TrackArtService.loadArtwork(activePlayer.trackArtUrl); } function maybeFinishSwitch() { @@ -138,10 +115,7 @@ Item { maybeFinishSwitch(); } function onTrackArtUrlChanged() { - if (activePlayer?.trackArtUrl) { - _lastArtUrl = activePlayer.trackArtUrl; - loadArtwork(activePlayer.trackArtUrl); - } + TrackArtService.loadArtwork(activePlayer.trackArtUrl); } } @@ -213,22 +187,6 @@ Item { } } - Process { - id: imageDownloader - running: false - property string targetFile: "" - - onExited: exitCode => { - if (exitCode === 0 && targetFile) - _bgArtSource = "file://" + targetFile; - } - } - - Process { - id: cleanupProcess - running: false - } - property bool isSeeking: false Timer { @@ -241,14 +199,14 @@ Item { Item { id: bgContainer anchors.fill: parent - visible: _bgArtSource !== "" + visible: TrackArtService._bgArtSource !== "" Image { id: bgImage anchors.centerIn: parent width: Math.max(parent.width, parent.height) * 1.1 height: width - source: _bgArtSource + source: TrackArtService._bgArtSource fillMode: Image.PreserveAspectCrop asynchronous: true cache: true diff --git a/quickshell/Modules/OSD/MediaPlaybackOSD.qml b/quickshell/Modules/OSD/MediaPlaybackOSD.qml index 02755882..5b908fb5 100644 --- a/quickshell/Modules/OSD/MediaPlaybackOSD.qml +++ b/quickshell/Modules/OSD/MediaPlaybackOSD.qml @@ -1,4 +1,6 @@ import QtQuick +import QtQuick.Layouts +import QtQuick.Effects import qs.Common import qs.Services import qs.Widgets @@ -10,7 +12,7 @@ DankOSD { readonly property bool useVertical: isVerticalLayout readonly property var player: MprisController.activePlayer - osdWidth: useVertical ? (40 + Theme.spacingS * 2) : Math.min(260, Screen.width - Theme.spacingM * 2) + osdWidth: useVertical ? (40 + Theme.spacingS * 2) : Math.min(280, Screen.width - Theme.spacingM * 2) osdHeight: useVertical ? (Theme.iconSize * 2) : (40 + Theme.spacingS * 2) autoHideInterval: 3000 enableMouseInteraction: true @@ -44,12 +46,17 @@ DankOSD { target: player function handleUpdate() { - if (!root.player?.trackTitle) return; + if (!root.player?.trackTitle) + return; if (SettingsData.osdMediaPlaybackEnabled) { + TrackArtService.loadArtwork(player.trackArtUrl); root.show(); } } + function onTrackArtUrlChanged() { + TrackArtService.loadArtwork(player.trackArtUrl); + } function onIsPlayingChanged() { handleUpdate(); } @@ -79,6 +86,67 @@ DankOSD { onClicked: root.hide() } + Item { + id: bgContainer + anchors.fill: parent + visible: TrackArtService._bgArtSource !== "" + + Image { + id: bgImage + anchors.centerIn: parent + width: Math.max(parent.width, parent.height) + height: width + source: TrackArtService._bgArtSource + fillMode: Image.PreserveAspectCrop + asynchronous: true + cache: true + visible: false + } + + Item { + id: blurredBg + anchors.fill: parent + visible: false + + MultiEffect { + anchors.centerIn: parent + width: bgImage.width + height: bgImage.height + source: bgImage + blurEnabled: true + blurMax: 64 + blur: 0.3 + saturation: -0.2 + brightness: -0.25 + } + } + + Rectangle { + id: bgMask + anchors.fill: parent + radius: Theme.cornerRadius + visible: false + layer.enabled: true + } + + MultiEffect { + anchors.fill: parent + source: blurredBg + maskEnabled: true + maskSource: bgMask + maskThresholdMin: 0.5 + maskSpreadAtMin: 1.0 + opacity: 0.7 + } + + Rectangle { + anchors.fill: parent + radius: Theme.cornerRadius + color: Theme.surface + opacity: 0.3 + } + } + Rectangle { width: Theme.iconSize height: Theme.iconSize @@ -107,17 +175,33 @@ DankOSD { } } - StyledText { - id: textItem + Column { x: parent.gap * 2 + Theme.iconSize width: parent.width - Theme.iconSize - parent.gap * 3 anchors.verticalCenter: parent.verticalCenter - text: player ? `${player.trackTitle || I18n.tr("Unknown Title")} • ${player.trackArtist || I18n.tr("Unknown Artist")}` : "" - font.pixelSize: Theme.fontSizeMedium - font.weight: Font.Medium - color: Theme.surfaceText - wrapMode: Text.Wrap - maximumLineCount: 3 + spacing: 3 + + StyledText { + id: topText + width: parent.width + text: player ? `${player.trackTitle || I18n.tr("Unknown Title")}` : "" + font.pixelSize: Theme.fontSizeMedium + font.weight: Font.Medium + color: Theme.surfaceText + wrapMode: Text.NoWrap + elide: Text.ElideRight + } + + StyledText { + id: bottomText + width: parent.width + text: player ? ((player.trackArtist || I18n.tr("Unknown Artist")) + (player.trackAlbum ? ` • ${player.trackAlbum}` : "")) : "" + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.Light + color: Theme.surfaceText + wrapMode: Text.NoWrap + elide: Text.ElideRight + } } } } diff --git a/quickshell/Services/TrackArtService.qml b/quickshell/Services/TrackArtService.qml new file mode 100644 index 00000000..4ea05ef1 --- /dev/null +++ b/quickshell/Services/TrackArtService.qml @@ -0,0 +1,64 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import Quickshell +import QtQuick + +import Quickshell.Io +import Quickshell.Services.Mpris + +Singleton { + id: root + + property string _lastArtUrl: "" + property string _bgArtSource: "" + + property string activeTrackArtFile: "" + + function loadArtwork(url) { + if (!url || url == "") { + _bgArtSource = ""; + _lastArtUrl = ""; + return; + } + if (url == _lastArtUrl) + return; + _lastArtUrl = url; + if (url.startsWith("http://") || url.startsWith("https://")) { + const filename = "/tmp/.dankshell/trackart_" + Date.now() + ".jpg"; + activeTrackArtFile = filename; + + cleanupProcess.command = ["sh", "-c", "mkdir -p /tmp/.dankshell && find /tmp/.dankshell -name 'trackart_*' ! -name '" + filename.split('/').pop() + "' -delete"]; + cleanupProcess.running = true; + + imageDownloader.command = ["curl", "-L", "-s", "--user-agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36", "-o", filename, url]; + imageDownloader.targetFile = filename; + imageDownloader.running = true; + return; + } + // otherwise + _bgArtSource = url; + } + + property MprisPlayer activePlayer: MprisController.activePlayer + + onActivePlayerChanged: { + loadArtwork(activePlayer.trackArtUrl); + } + + Process { + id: imageDownloader + running: false + property string targetFile: "" + + onExited: exitCode => { + if (exitCode === 0 && targetFile) + _bgArtSource = "file://" + targetFile; + } + } + + Process { + id: cleanupProcess + running: false + } +}