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:
parent
9d16d6e6b0
commit
b18328bbad
96 changed files with 24119 additions and 0 deletions
|
|
@ -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: "%"
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
# Media Player Plugin for Dank Material Shell
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
|
@ -0,0 +1 @@
|
|||
ref: refs/heads/main
|
||||
|
|
@ -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
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
4d73e6c810f85093339eb6cc6dad251ea6c15b04
|
||||
|
|
@ -0,0 +1 @@
|
|||
4d73e6c810f85093339eb6cc6dad251ea6c15b04
|
||||
|
|
@ -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 |
Loading…
Add table
Add a link
Reference in a new issue