Add .config/DankMaterialShell/firefox.css

Add .config/DankMaterialShell/plugin_settings.json
Add .config/DankMaterialShell/plugins/dankDesktopWeather.meta
Add .config/DankMaterialShell/plugins/dankHooks.meta
Add .config/DankMaterialShell/plugins/desktopCommand/LICENSE
Add .config/DankMaterialShell/plugins/desktopCommand/README.md
Add .config/DankMaterialShell/plugins/desktopCommand/Settings.qml
Add .config/DankMaterialShell/plugins/desktopCommand/Widget.qml
Add .config/DankMaterialShell/plugins/desktopCommand/assets/screenshot.jpg
Add .config/DankMaterialShell/plugins/desktopCommand/.git/HEAD
Add .config/DankMaterialShell/plugins/desktopCommand/.git/config
Add .config/DankMaterialShell/plugins/desktopCommand/.git/index
Add .config/DankMaterialShell/plugins/desktopCommand/.git/objects/info/.keep
Add .config/DankMaterialShell/plugins/desktopCommand/.git/objects/pack/pack-c2ca48eacecc3ab45931476641d058a89d755775.idx
Add .config/DankMaterialShell/plugins/desktopCommand/.git/objects/pack/pack-c2ca48eacecc3ab45931476641d058a89d755775.rev
Add .config/DankMaterialShell/plugins/desktopCommand/.git/objects/pack/pack-c2ca48eacecc3ab45931476641d058a89d755775.pack
Add .config/DankMaterialShell/plugins/desktopCommand/.git/refs/heads/main
Add .config/DankMaterialShell/plugins/desktopCommand/.git/refs/remotes/origin/main
Add .config/DankMaterialShell/plugins/desktopCommand/.git/refs/tags/.keep
Add .config/DankMaterialShell/plugins/desktopCommand/.gitignore
Add .config/DankMaterialShell/plugins/desktopCommand/wrapCommand
Add .config/DankMaterialShell/plugins/desktopCommand/plugin.json
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankActions/DankActionsSettings.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankActions/DankActionsWidget.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankActions/plugin.json
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankBatteryAlerts/DankBatteryAlerts.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankBatteryAlerts/DankBatteryAlertsSettings.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankBatteryAlerts/plugin.json
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankDesktopWeather/DankDesktopWeather.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankDesktopWeather/DankDesktopWeatherSettings.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankDesktopWeather/plugin.json
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankHooks/DankHooks.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankHooks/DankHooksSettings.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankHooks/README.md
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankHooks/plugin.json
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankPomodoroTimer/DankPomodoroSettings.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankPomodoroTimer/DankPomodoroWidget.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankPomodoroTimer/plugin.json
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/LICENSE
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/README.md
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/HEAD
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/config
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/index
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/info/.keep
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-3221a15c022ef4a7bb6bf2c47e40068b66b3588b.idx
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-3221a15c022ef4a7bb6bf2c47e40068b66b3588b.rev
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-9aca069a8b76b40fcc472eba1ed9b8219a87776b.idx
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-9aca069a8b76b40fcc472eba1ed9b8219a87776b.rev
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-e6f6cdfe9914bfb4a5717ef6036821794d59ab4b.idx
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-e6f6cdfe9914bfb4a5717ef6036821794d59ab4b.rev
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-3221a15c022ef4a7bb6bf2c47e40068b66b3588b.pack
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-9aca069a8b76b40fcc472eba1ed9b8219a87776b.pack
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-e6f6cdfe9914bfb4a5717ef6036821794d59ab4b.pack
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/refs/heads/master
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/refs/remotes/origin/master
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/refs/tags/.keep
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.gitignore
Add .config/DankMaterialShell/plugins/emojiLauncher/EmojiLauncher.qml
Add .config/DankMaterialShell/plugins/emojiLauncher/EmojiLauncherSettings.qml
Add .config/DankMaterialShell/plugins/emojiLauncher/LICENSE
Add .config/DankMaterialShell/plugins/emojiLauncher/README.md
Add .config/DankMaterialShell/plugins/emojiLauncher/catalog.js
Add .config/DankMaterialShell/plugins/emojiLauncher/data/emojis.txt
Add .config/DankMaterialShell/plugins/emojiLauncher/data/math.txt
Add .config/DankMaterialShell/plugins/emojiLauncher/data/nerdfont.txt
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/HEAD
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/config
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/index
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/objects/info/.keep
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/objects/pack/pack-e04a5b1ea381dc3a792b8bf08cf70e735b195c0d.idx
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/objects/pack/pack-e04a5b1ea381dc3a792b8bf08cf70e735b195c0d.rev
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/objects/pack/pack-e04a5b1ea381dc3a792b8bf08cf70e735b195c0d.pack
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/refs/heads/main
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/refs/remotes/origin/main
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/refs/tags/.keep
Add .config/DankMaterialShell/plugins/emojiLauncher/plugin.json
Add .config/DankMaterialShell/plugins/emojiLauncher/screenshot.png
Add .config/DankMaterialShell/plugins/emojiLauncher/scripts/generate_catalog.py
Add .config/DankMaterialShell/plugins/mediaPlayer/MediaPlayerSettings.qml
Add .config/DankMaterialShell/plugins/mediaPlayer/MediaPlayerTab.qml
Add .config/DankMaterialShell/plugins/mediaPlayer/README.md
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/HEAD
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/config
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/index
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/objects/info/.keep
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/objects/pack/pack-0b9cb33f7da23f6ff361ee3aa5117928714bc3be.idx
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/objects/pack/pack-0b9cb33f7da23f6ff361ee3aa5117928714bc3be.rev
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/objects/pack/pack-0b9cb33f7da23f6ff361ee3aa5117928714bc3be.pack
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/refs/heads/main
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/refs/remotes/origin/main
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/refs/tags/.keep
Add .config/DankMaterialShell/plugins/mediaPlayer/plugin.json
Add .config/DankMaterialShell/plugins/mediaPlayer/screenshot_8.png
Add .config/DankMaterialShell/plugins/dankDesktopWeather
Add .config/DankMaterialShell/plugins/dankHooks
Add .config/DankMaterialShell/settings.json
This commit is contained in:
Lewis Wynne 2026-01-07 15:09:11 +00:00
parent 9d16d6e6b0
commit b18328bbad
96 changed files with 24119 additions and 0 deletions

View file

@ -0,0 +1,27 @@
import Quickshell
import QtQuick
import qs.Common
import qs.Modules.Plugins
PluginSettings {
id: root
pluginId: "mediaPlayer"
SliderSetting {
settingKey: "backgroundOpacity"
label: I18n.tr("Background Opacity")
defaultValue: 80
minimum: 0
maximum: 100
unit: "%"
}
SliderSetting {
settingKey: "borderOpacity"
label: I18n.tr("Border Opacity")
defaultValue: 100
minimum: 0
maximum: 100
unit: "%"
}
}

View file

@ -0,0 +1,693 @@
import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import Quickshell.Services.Mpris
import Quickshell.Io
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Plugins
DesktopPluginComponent {
id: root
// settings data here
property real backgroundOpacity: (pluginData.backgroundOpacity ?? 80) / 100
property real borderOpacity: (pluginData.borderOpacity ?? 100) / 100
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
opacity: showNoPlayerNow ? 0 : 1
Behavior on opacity { NumberAnimation { duration: 300 } }
property MprisPlayer activePlayer: MprisController.activePlayer
property var allPlayers: MprisController.availablePlayers
property bool isSwitching: false
property string _lastArtUrl: ""
property string _bgArtSource: ""
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;
}
function maybeFinishSwitch() {
if (activePlayer && activePlayer.trackTitle !== "") {
isSwitching = false;
_switchHold = false;
_stalePositionDetected = false;
}
}
function getDisplayPosition() {
if (!activePlayer) return 0;
const rawPos = Math.max(0, activePlayer.position || 0);
const length = Math.max(1, activePlayer.length || 1);
// If we detected stale position, show 0 until proper data arrives
if (_stalePositionDetected) {
return 0;
}
// Handle stale position data when switching videos
if (isSwitching && rawPos >= length * 0.9) {
return 0;
}
const pos = activePlayer.length ? rawPos % Math.max(1, activePlayer.length) : rawPos;
return pos;
}
function formatTime(seconds) {
const minutes = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return minutes + ":" + (secs < 10 ? "0" : "") + secs;
}
Component.onCompleted: {
// Initialize with current player state if available
if (activePlayer) {
// Get actual position after MPRIS fully loads
Qt.callLater(() => {
try {
const actualPos = activePlayer.position || 0;
const length = activePlayer.length || 1;
root._positionSnapshot = actualPos;
if (progressSeekbar && actualPos > 0) {
progressSeekbar.value = Math.min(1, actualPos / length);
}
} catch (e) {
// Handle MPRIS errors
}
});
}
}
// Derived "no players" state: always correct, no timers.
readonly property int _playerCount: allPlayers ? allPlayers.length : 0
readonly property bool _noneAvailable: _playerCount === 0
readonly property bool _trulyIdle: activePlayer && activePlayer.playbackState === MprisPlaybackState.Stopped && !activePlayer.trackTitle && !activePlayer.trackArtist
readonly property bool showNoPlayerNow: (!_switchHold) && (_noneAvailable || _trulyIdle)
property bool _switchHold: false
Timer {
id: _switchHoldTimer
interval: 650
repeat: false
onTriggered: {
_switchHold = false;
if (isSwitching) {
isSwitching = false;
}
}
}
onActivePlayerChanged: {
root._positionSnapshot = 0;
root._forceUpdate = !root._forceUpdate;
if (!activePlayer) {
isSwitching = false;
_switchHold = false;
return;
}
isSwitching = true;
_switchHold = true;
_switchHoldTimer.restart();
if (activePlayer.trackArtUrl)
loadArtwork(activePlayer.trackArtUrl);
// Get actual current position after a short delay to allow MPRIS to sync
Qt.callLater(() => {
try {
const actualPos = activePlayer.position || 0;
root._positionSnapshot = actualPos;
if (progressSeekbar && actualPos > 0) {
progressSeekbar.value = Math.min(1, actualPos / Math.max(1, activePlayer.length || 1));
isSwitching = false;
}
} catch (e) {
// Handle errors gracefully
}
});
}
// Responsive sizing with min/max constraints
property real userScale: 1.0
readonly property real minWidth: 320
readonly property real maxWidth: 800
readonly property real minHeight: 160
readonly property real maxHeight: 400
readonly property real baseWidth: 380
readonly property real baseHeight: 200
implicitWidth: Math.max(minWidth, Math.min(maxWidth, baseWidth * userScale))
implicitHeight: Math.max(minHeight, Math.min(maxHeight, baseHeight * userScale))
Connections {
target: activePlayer
function onTrackTitleChanged() {
root._positionSnapshot = 0;
root._forceUpdate = !root._forceUpdate;
// Force immediate position reset for new track
if (activePlayer.position > 0 && activePlayer.length > 0) {
const progressRatio = activePlayer.position / activePlayer.length;
if (progressRatio > 0.9) {
// Likely stale data - force reset
root._stalePositionDetected = true;
}
}
_switchHoldTimer.restart();
maybeFinishSwitch();
// Reset progress bar immediately on track change
if (progressSeekbar) {
progressSeekbar.value = 0;
}
}
function onTrackArtUrlChanged() {
if (activePlayer?.trackArtUrl) {
_lastArtUrl = activePlayer.trackArtUrl;
loadArtwork(activePlayer.trackArtUrl);
}
}
function onPositionChanged() {
try {
if (root._stalePositionDetected && activePlayer.position < activePlayer.length * 0.5) {
// Position updated properly now
root._stalePositionDetected = false;
root._forceUpdate = !root._forceUpdate;
}
} catch (e) {
// MPRIS service disappeared - reset state
root._stalePositionDetected = false;
}
}
}
Connections {
target: MprisController
function onAvailablePlayersChanged() {
const count = (MprisController.availablePlayers?.length || 0);
if (count === 0) {
isSwitching = false;
_switchHold = false;
} else {
_switchHold = true;
_switchHoldTimer.restart();
}
}
}
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
property real _positionSnapshot: 0
property bool _forceUpdate: false
property real _animationTick: 0
property bool _stalePositionDetected: false
Timer {
id: positionUpdateTimer
interval: 100
running: true
repeat: true
onTriggered: {
// Update snapshot to trigger binding re-evaluation
if (activePlayer) {
try {
const newPosition = activePlayer.position || 0;
root._positionSnapshot = newPosition;
// Force progress bar refresh when switching
if (isSwitching || _stalePositionDetected) {
if (progressSeekbar) {
progressSeekbar.value = progressSeekbar.calculateProgress();
}
}
} catch (e) {
// Handle MPRIS service errors gracefully
root._positionSnapshot = 0;
}
}
}
}
// Use animation to drive constant updates for smooth progress bar
NumberAnimation {
id: progressUpdateAnimation
target: root
property: "_animationTick"
from: 0
to: 10000
duration: 10000
loops: Animation.Infinite
running: activePlayer?.playbackState === MprisPlaybackState.Playing && !isSeeking
}
Item {
id: bgContainer
anchors.fill: parent
visible: _bgArtSource !== ""
Image {
id: bgImage
anchors.centerIn: parent
width: Math.max(parent.width, parent.height) * 1.1
height: width
// source: _bgArtSource
fillMode: Image.PreserveAspectCrop
asynchronous: true
cache: true
visible: false
onStatusChanged: {
if (status === Image.Ready)
maybeFinishSwitch();
}
}
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: 2
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.withAlpha(Theme.surface, root.backgroundOpacity)
}
}
// --- Ocean Wave Background ---
Canvas {
id: waveCanvas
anchors.fill: parent
z: 1 // Ensures it stays behind the main content
opacity: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing ? 0.4 : 0.1
property real phase: 0
// This timer drives the animation "movement"
Timer {
interval: 16 // ~60 FPS
running: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing
repeat: true
onTriggered: {
waveCanvas.phase += 0.05;
waveCanvas.requestPaint();
}
}
onPaint: {
var ctx = getContext("2d");
ctx.clearRect(0, 0, width, height);
// Draw two overlapping waves for a "deep" ocean feel
drawWave(ctx, "#40" + Theme.primary.toString().substring(1), 0.5, 15, phase);
drawWave(ctx, "#60" + Theme.primary.toString().substring(1), 0.8, 10, phase * 0.7);
drawWave(ctx, "#70" + Theme.primary.toString().substring(1), 0.8, 10, phase * 0.9);
}
function drawWave(ctx, color, speed, amplitude, currentPhase) {
ctx.beginPath();
ctx.fillStyle = color;
var waveHeight = height * 0.7; // Position waves at the bottom 30%
ctx.moveTo(0, height);
for (var x = 0; x <= width; x += 5) {
// Sine wave calculation: y = amplitude * sin(frequency * x + phase)
var y = waveHeight + Math.sin(x * 0.02 + currentPhase) * amplitude;
ctx.lineTo(x, y);
}
ctx.lineTo(width, height);
ctx.closePath();
ctx.fill();
}
}
// --- End Ocean Wave Background ---
Column {
anchors.centerIn: parent
spacing: Theme.spacingM
visible: showNoPlayerNow
DankIcon {
name: "music_note"
size: Theme.iconSize * 3
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
anchors.horizontalCenter: parent.horizontalCenter
}
}
// Main content container - Layout with thumbnail
Item {
anchors.fill: parent
anchors.margins: Theme.spacingM * userScale
visible: !_noneAvailable && (!showNoPlayerNow)
Row {
anchors.fill: parent
spacing: Theme.spacingM * userScale
// Album Thumbnail Section (Left)
Rectangle {
id: thumbnailContainer
width: parent.height * 0.85
height: parent.height * 0.85
anchors.verticalCenter: parent.verticalCenter
radius: 6 * userScale
color: "transparent"
clip: true
property real albumRotation: 0
NumberAnimation {
id: rotationAnimation
target: thumbnailContainer
property: "albumRotation"
from: 0
to: 360
duration: 20000
running: activePlayer?.playbackState === MprisPlaybackState.Playing
loops: Animation.Infinite
}
DankAlbumArt {
id: albumArt
width: parent.width * 0.76
height: parent.height * 0.76
anchors.centerIn: parent
activePlayer: root.activePlayer
rotation: thumbnailContainer.albumRotation
}
// Subtle border
Rectangle {
anchors.fill: parent
radius: parent.radius
color: "transparent"
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, root.borderOpacity)
border.width: 1
}
}
// Content Section (Right)
Column {
width: parent.width - thumbnailContainer.width - parent.spacing
height: parent.height
spacing: Theme.spacingS * userScale
// Song Info Section (Top)
Column {
id: songInfo
width: parent.width
spacing: 2 * userScale
StyledText {
text: activePlayer?.trackTitle || "The (Overdue) Collapse of Wind..."
font.pixelSize: Theme.fontSizeMedium * 1.1 * userScale
font.weight: Font.Bold
color: Theme.surfaceText
width: parent.width
elide: Text.ElideRight
maximumLineCount: 1
}
StyledText {
text: activePlayer?.trackArtist || "Catalyst"
font.pixelSize: Theme.fontSizeSmall * userScale
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
width: parent.width
elide: Text.ElideRight
maximumLineCount: 1
}
}
// Spacer
Item {
width: parent.width
height: Theme.spacingXS * userScale
}
// Controls Row (Middle)
Row {
id: controlsRow
width: parent.width
spacing: Theme.spacingS * userScale
// Previous Button
Rectangle {
width: 32 * userScale
height: 32 * userScale
radius: 4 * userScale
color: "transparent"
anchors.verticalCenter: parent.verticalCenter
DankIcon {
anchors.centerIn: parent
name: "skip_previous"
size: 28 * userScale
color: Theme.primary
}
MouseArea {
id: prevBtnArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (!activePlayer)
return;
if (activePlayer.position > 8 && activePlayer.canSeek) {
activePlayer.position = 0;
} else {
activePlayer.previous();
}
}
}
}
// Play/Pause Button
Rectangle {
width: 32 * userScale
height: 32 * userScale
radius: 4 * userScale
color: "transparent"
anchors.verticalCenter: parent.verticalCenter
DankIcon {
anchors.centerIn: parent
name: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow"
size: 28 * userScale
color: Theme.primary
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: activePlayer && activePlayer.togglePlaying()
}
}
// Next Button
Rectangle {
width: 32 * userScale
height: 32 * userScale
radius: 4 * userScale
color: "transparent"
anchors.verticalCenter: parent.verticalCenter
DankIcon {
anchors.centerIn: parent
name: "skip_next"
size: 28 * userScale
color: Theme.primary
}
MouseArea {
id: nextBtnArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: activePlayer && activePlayer.next()
}
}
// Spacer to push buttons to right
Item {
width: parent.width - (32 * 3 * userScale) - (Theme.spacingS * 3 * userScale)
height: 1
}
}
// Seekbar Section (Bottom)
Column {
id: seekbarSection
width: parent.width
spacing: 2 * userScale
// Integrated DankSeekbar
DankSeekbar {
id: progressSeekbar
width: parent.width
height: 16 * userScale // Consistent with previous design
activePlayer: root.activePlayer
// Keep the Tab's seeking state in sync with the component
onIsSeekingChanged: root.isSeeking = isSeeking
function calculateProgress() {
if (!root.activePlayer || root.activePlayer.length <= 0) return 0;
const rawPos = Math.max(0, root.activePlayer.position || 0);
const length = Math.max(1, root.activePlayer.length || 1);
// If we detected stale position, show 0 until proper data arrives
if (root._stalePositionDetected) {
// Check if position is now valid
if (rawPos < length * 0.8) {
root._stalePositionDetected = false;
root.isSwitching = false;
} else {
return 0;
}
}
// Reset if position seems stale (at end for new video)
if (root.isSwitching && rawPos >= length * 0.9) {
root._stalePositionDetected = true;
return 0;
}
// Force position refresh when switching videos
if (root.isSwitching && rawPos > 0 && rawPos < length * 0.8) {
root.isSwitching = false;
}
return Math.min(1, rawPos / length);
}
// Connect position updates to seekbar value directly
Timer {
interval: 50
running: true
repeat: true
onTriggered: {
if (progressSeekbar && activePlayer) {
try {
progressSeekbar.value = progressSeekbar.calculateProgress();
} catch (e) {
// Handle MPRIS errors
}
}
}
}
}
// Time labels
Item {
width: parent.width
height: 12 * userScale
StyledText {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
text: {
// Force dependency on position updates
root._positionSnapshot;
return formatTime(getDisplayPosition());
}
font.pixelSize: Theme.fontSizeSmall * 0.9 * userScale
color: Theme.surfaceVariantText
}
StyledText {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
text: {
if (!activePlayer || !activePlayer.length)
return "0:00";
const dur = Math.max(0, activePlayer.length || 0);
const minutes = Math.floor(dur / 60);
const seconds = Math.floor(dur % 60);
return minutes + ":" + (seconds < 10 ? "0" : "") + seconds;
}
font.pixelSize: Theme.fontSizeSmall * 0.9 * userScale
color: Theme.surfaceVariantText
}
}
}
}
}
}
}

View file

@ -0,0 +1,51 @@
# Media Player Plugin for Dank Material Shell
![Media Player Screenshot](screenshot_8.png)
A feature-rich media player widget for Dank Material Shell with MPRIS support, volume control, and a beautiful, responsive interface.
## Features
- **MPRIS Integration**: Control any media player that supports the MPRIS D-Bus interface (e.g., Spotify, VLC, MPV, Firefox).
- **Beautiful Visuals**:
- **Dynamic Album Art**: Displays track artwork with a smooth rotation animation during playback.
- **Animated Ocean Wave**: A unique, animated wave background that reacts to the playback state.
- **Blurred Background**: Uses the current track's artwork to create a cohesive, blurred background effect.
- **Playback Controls**:
- Play/Pause toggle.
- Skip to previous and next tracks.
- Smooth seekbar for precise track navigation.
- **Responsive Design**: Automatically adjusts its layout and scaling to fit different screen sizes and user preferences.
- **Remote Artwork Support**: Automatically downloads and caches high-quality artwork from web URLs.
- **Customizable Settings**: Adjust the background opacity to match your desktop theme.
## Configuration
You can customize the following settings in the plugin configuration:
- **Background Opacity**: Adjust the transparency of the player's background (0% to 100%).
## Technical Details
- **Type**: Desktop Widget
- **Capabilities**: `desktop-widget`, `media-player`, `mpris`, `audio-control`
- **Permissions**:
- `settings_read` / `settings_write`: To manage user preferences.
- `mpris_control`: To interact with media players.
- `audio_control`: For system volume management.
## Note
> [!NOTE]
> The media player widget will only be visible when there is an active media player (e.g., Spotify, VLC, Firefox) running and playing media. If no active media session is detected, the widget will automatically hide itself.
## Installation
Ensure this plugin is placed in your Dank Material Shell plugins directory:
`~/.config/DankMaterialShell/plugins/mediaPlayer`
## Requirements
- **Quickshell**: The underlying shell framework.
- **MPRIS compatible player**: Required for media control.
- **Curl**: Used for downloading remote album art.

View file

@ -0,0 +1 @@
ref: refs/heads/main

View file

@ -0,0 +1,9 @@
[core]
bare = false
filemode = true
[remote "origin"]
url = https://github.com/arrifat346afs/mediaPlayer
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
remote = origin
merge = refs/heads/main

View file

@ -0,0 +1 @@
4d73e6c810f85093339eb6cc6dad251ea6c15b04

View file

@ -0,0 +1 @@
4d73e6c810f85093339eb6cc6dad251ea6c15b04

View file

@ -0,0 +1,18 @@
{
"id": "mediaPlayer",
"name": "Media Player",
"description": "A feature-rich media player widget with MPRIS support, volume control, playlist management, and audio device switching",
"version": "1.0.0",
"author": "arrifat346afs",
"type": "desktop",
"capabilities": ["desktop-widget", "media-player", "mpris", "audio-control"],
"component": "./MediaPlayerTab.qml",
"settings": "./MediaPlayerSettings.qml",
"icon": "music_note",
"permissions": [
"settings_read",
"settings_write",
"mpris_control",
"audio_control"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 KiB