mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-16 16:24:00 -04:00
347 lines
11 KiB
QML
347 lines
11 KiB
QML
import QtQuick
|
|
import Quickshell.Services.Mpris
|
|
import qs.Common
|
|
import qs.Services
|
|
import qs.Modules.DDash.Media
|
|
|
|
Item {
|
|
id: root
|
|
|
|
LayoutMirroring.enabled: I18n.isRtl
|
|
LayoutMirroring.childrenInherit: true
|
|
|
|
property MprisPlayer activePlayer: MprisController.activePlayer
|
|
readonly property real stableLength: MprisController.activePlayerStableLength
|
|
property var allPlayers: MprisController.availablePlayers
|
|
property bool live: Window.window?.visible ?? false
|
|
property bool audioRefHeld: false
|
|
property string entryId: "media"
|
|
readonly property var options: DashRegistry.resolvedOptions(entryId)
|
|
readonly property string playerStyle: options?.playerStyle ?? MediaOptions.defaultPlayerStyle
|
|
readonly property bool lyricsEnabled: (options?.lyrics ?? MediaOptions.defaults.lyrics) && DMSService.capabilities.includes("lyrics")
|
|
readonly property bool smoothLyrics: options?.smoothLyrics ?? MediaOptions.defaults.smoothLyrics
|
|
property bool lyricsOpen: false
|
|
property bool playerPaneOpen: true
|
|
property Item lyricsFocusTarget: null
|
|
property Item lyricsOpener: null
|
|
readonly property var presentation: mediaPresentation.current
|
|
readonly property bool idle: !presentation
|
|
property bool wallpaperEnabled: MediaOptions.albumArtBackdrop
|
|
property string panel: ""
|
|
property Item contentViewport: null
|
|
readonly property bool blocksTabNavigation: panel !== ""
|
|
property bool isSeeking: false
|
|
property real previousVolume: 0.0
|
|
|
|
readonly property color accent: MediaAccentService.accent
|
|
|
|
readonly property Item focusTarget: mediaChrome.item?.focusTarget ?? null
|
|
readonly property Item previousFocusTarget: mediaChrome.item?.previousFocusTarget ?? null
|
|
|
|
readonly property bool __isChromeBrowser: {
|
|
if (!activePlayer?.identity)
|
|
return false;
|
|
const id = activePlayer.identity.toLowerCase();
|
|
return id.includes("chrome") || id.includes("chromium");
|
|
}
|
|
readonly property bool volumeAvailable: !!((activePlayer && activePlayer.volumeSupported && !__isChromeBrowser) || (AudioService.sink && AudioService.sink.audio))
|
|
readonly property bool usePlayerVolume: activePlayer && activePlayer.volumeSupported && !__isChromeBrowser
|
|
readonly property real reportedVolume: usePlayerVolume ? activePlayer.volume : (AudioService.sink?.audio?.volume ?? 0)
|
|
readonly property real currentVolume: pendingVolume >= 0 ? pendingVolume : reportedVolume
|
|
readonly property real maxVolumePercent: usePlayerVolume ? 100 : AudioService.sinkMaxVolume
|
|
|
|
// MPRIS volume round-trips through the player, so successive steps would each read a stale
|
|
// activePlayer.volume and collapse into one. Drive them from the value we last asked for.
|
|
property real pendingVolume: -1
|
|
|
|
onReportedVolumeChanged: {
|
|
if (pendingVolume >= 0 && Math.abs(reportedVolume - pendingVolume) < 0.005)
|
|
clearPendingVolume();
|
|
}
|
|
|
|
onUsePlayerVolumeChanged: clearPendingVolume()
|
|
onActivePlayerChanged: clearPendingVolume()
|
|
|
|
implicitWidth: DashMetrics.contentWidthFor(SettingsData.showWeekNumber, DashMetrics.panelColumnsFor(entryId))
|
|
implicitHeight: mediaChrome.item?.implicitHeight ?? DashMetrics.tabMinHeight
|
|
|
|
onPlayerStyleChanged: {
|
|
panel = "";
|
|
lyricsOpen = false;
|
|
playerPaneOpen = true;
|
|
isSeeking = false;
|
|
restoreLyrics();
|
|
}
|
|
|
|
onLyricsEnabledChanged: {
|
|
if (lyricsEnabled) {
|
|
restoreLyrics();
|
|
return;
|
|
}
|
|
lyricsOpen = false;
|
|
playerPaneOpen = true;
|
|
}
|
|
|
|
onIdleChanged: {
|
|
if (!idle)
|
|
return;
|
|
panel = "";
|
|
isSeeking = false;
|
|
}
|
|
|
|
onLyricsOpenChanged: {
|
|
if (!lyricsOpen)
|
|
lyricsFocusTimer.restart();
|
|
}
|
|
|
|
Timer {
|
|
id: lyricsFocusTimer
|
|
interval: 0
|
|
onTriggered: {
|
|
if (!root.live || !root.lyricsOpener?.visible || !root.lyricsOpener.enabled)
|
|
return;
|
|
if (typeof root.lyricsOpener.requestFocus === "function") {
|
|
root.lyricsOpener.requestFocus(false, Qt.OtherFocusReason);
|
|
return;
|
|
}
|
|
root.lyricsOpener.forceActiveFocus(Qt.OtherFocusReason);
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
id: pendingVolumeTimer
|
|
interval: DashMetrics.mediaVolumeEchoTimeout
|
|
onTriggered: root.pendingVolume = -1
|
|
}
|
|
|
|
MediaPresentation {
|
|
id: mediaPresentation
|
|
player: root
|
|
}
|
|
|
|
Timer {
|
|
interval: DashMetrics.mediaPositionPollInterval
|
|
running: root.live && root.activePlayer?.playbackState === MprisPlaybackState.Playing && !root.isSeeking
|
|
repeat: true
|
|
onTriggered: root.activePlayer?.positionChanged()
|
|
}
|
|
|
|
function syncAudioRef(wanted) {
|
|
if (wanted === audioRefHeld)
|
|
return;
|
|
audioRefHeld = wanted;
|
|
if (wanted) {
|
|
AudioService.addRef();
|
|
return;
|
|
}
|
|
AudioService.removeRef();
|
|
}
|
|
|
|
Component.onCompleted: syncAudioRef(live)
|
|
Component.onDestruction: syncAudioRef(false)
|
|
|
|
onLiveChanged: {
|
|
syncAudioRef(live);
|
|
if (live) {
|
|
restoreLyrics();
|
|
return;
|
|
}
|
|
panel = "";
|
|
lyricsOpen = false;
|
|
playerPaneOpen = true;
|
|
}
|
|
|
|
function cycleFocus(backwards) {
|
|
return mediaChrome.item?.cycleFocus(backwards) ?? false;
|
|
}
|
|
|
|
function togglePanel(panelId) {
|
|
panel = panel === panelId ? "" : panelId;
|
|
}
|
|
|
|
function showPanel(panelId) {
|
|
panel = panelId;
|
|
}
|
|
|
|
function revealVolume() {
|
|
if (mediaChrome.item?.inlineVolume)
|
|
return;
|
|
showPanel("volume");
|
|
}
|
|
|
|
function restoreLyrics() {
|
|
if (live && lyricsEnabled && CacheData.mediaLyricsOpen)
|
|
lyricsOpen = true;
|
|
}
|
|
|
|
function toggleLyrics(opener) {
|
|
if (!lyricsEnabled)
|
|
return;
|
|
lyricsOpener = opener ?? null;
|
|
lyricsOpen = !lyricsOpen;
|
|
CacheData.set("mediaLyricsOpen", lyricsOpen);
|
|
lyricsFocusTimer.stop();
|
|
}
|
|
|
|
function getVolumeIcon() {
|
|
if (!volumeAvailable)
|
|
return "volume_off";
|
|
if (usePlayerVolume)
|
|
return currentVolume === 0 ? "music_off" : "music_note";
|
|
return AudioService.sinkVolumeIconName;
|
|
}
|
|
|
|
function clearPendingVolume() {
|
|
pendingVolume = -1;
|
|
pendingVolumeTimer.stop();
|
|
}
|
|
|
|
function setVolume(ratio) {
|
|
if (!volumeAvailable)
|
|
return;
|
|
const clamped = Math.min(maxVolumePercent / 100, Math.max(0, ratio));
|
|
SessionData.suppressOSDTemporarily();
|
|
if (usePlayerVolume) {
|
|
pendingVolume = clamped;
|
|
pendingVolumeTimer.restart();
|
|
activePlayer.volume = clamped;
|
|
return;
|
|
}
|
|
const audio = AudioService.sink?.audio;
|
|
if (!audio)
|
|
return;
|
|
audio.volume = clamped;
|
|
if (clamped > 0)
|
|
audio.muted = false;
|
|
}
|
|
|
|
function adjustVolume(step) {
|
|
setVolume((Math.round(currentVolume * 100) + step) / 100);
|
|
}
|
|
|
|
function cycleLoopState() {
|
|
if (!activePlayer?.canControl || !activePlayer.loopSupported)
|
|
return;
|
|
switch (activePlayer.loopState) {
|
|
case MprisLoopState.None:
|
|
activePlayer.loopState = MprisLoopState.Playlist;
|
|
break;
|
|
case MprisLoopState.Playlist:
|
|
activePlayer.loopState = MprisLoopState.Track;
|
|
break;
|
|
case MprisLoopState.Track:
|
|
activePlayer.loopState = MprisLoopState.None;
|
|
break;
|
|
}
|
|
}
|
|
|
|
function toggleMute() {
|
|
if (!volumeAvailable)
|
|
return;
|
|
if (!usePlayerVolume) {
|
|
SessionData.suppressOSDTemporarily();
|
|
AudioService.sink.audio.muted = !AudioService.sink.audio.muted;
|
|
return;
|
|
}
|
|
if (currentVolume > 0) {
|
|
root.previousVolume = currentVolume;
|
|
setVolume(0);
|
|
return;
|
|
}
|
|
setVolume(root.previousVolume > 0 ? root.previousVolume : 0.5);
|
|
}
|
|
|
|
function handleKeyEvent(event) {
|
|
if (event.key === Qt.Key_F6)
|
|
return cycleFocus(!!(event.modifiers & Qt.ShiftModifier));
|
|
if (event.key === Qt.Key_Escape) {
|
|
if (panel === "")
|
|
return false;
|
|
const panelId = panel;
|
|
panel = "";
|
|
mediaChrome.item?.focusPanelButton(panelId);
|
|
return true;
|
|
}
|
|
if (panel !== "")
|
|
return true;
|
|
if (!activePlayer)
|
|
return false;
|
|
|
|
if (event.key >= Qt.Key_0 && event.key <= Qt.Key_9) {
|
|
if (!activePlayer.canSeek || stableLength <= 0)
|
|
return false;
|
|
const targetPosition = (event.key - Qt.Key_0) * 0.1 * stableLength;
|
|
activePlayer.position = Math.max(0.1, Math.min(targetPosition, stableLength * 0.99));
|
|
return true;
|
|
}
|
|
|
|
switch (event.key) {
|
|
case Qt.Key_Left:
|
|
case Qt.Key_H:
|
|
if (!activePlayer.canSeek)
|
|
return false;
|
|
activePlayer.position = Math.max(0.1, activePlayer.position - 5);
|
|
return true;
|
|
case Qt.Key_Right:
|
|
case Qt.Key_L:
|
|
if (!activePlayer.canSeek || stableLength <= 0)
|
|
return false;
|
|
activePlayer.position = Math.max(0.1, Math.min(stableLength - 1, activePlayer.position + 5));
|
|
return true;
|
|
case Qt.Key_Up:
|
|
case Qt.Key_K:
|
|
if (!volumeAvailable)
|
|
return false;
|
|
adjustVolume(AudioService.wheelVolumeStep);
|
|
revealVolume();
|
|
return true;
|
|
case Qt.Key_Down:
|
|
case Qt.Key_J:
|
|
if (!volumeAvailable)
|
|
return false;
|
|
adjustVolume(-AudioService.wheelVolumeStep);
|
|
revealVolume();
|
|
return true;
|
|
case Qt.Key_Space:
|
|
if (!activePlayer.canTogglePlaying)
|
|
return false;
|
|
activePlayer.togglePlaying();
|
|
return true;
|
|
case Qt.Key_M:
|
|
if (!volumeAvailable)
|
|
return false;
|
|
toggleMute();
|
|
revealVolume();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Loader {
|
|
id: mediaChrome
|
|
anchors.fill: parent
|
|
active: !root.idle
|
|
sourceComponent: root.playerStyle === "material" ? materialChrome : bentoChrome
|
|
}
|
|
|
|
Loader {
|
|
anchors.fill: parent
|
|
active: root.idle
|
|
sourceComponent: MediaEmptyState {}
|
|
}
|
|
|
|
Component {
|
|
id: bentoChrome
|
|
BentoChrome {
|
|
player: root
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: materialChrome
|
|
MaterialChrome {
|
|
player: root
|
|
}
|
|
}
|
|
}
|