diff --git a/dank-qml-common b/dank-qml-common index 640b739e3..27e67d6a0 160000 --- a/dank-qml-common +++ b/dank-qml-common @@ -1 +1 @@ -Subproject commit 640b739e31709d9aa7d5b9d26f511babc9b95daf +Subproject commit 27e67d6a0cc3d6ea211fe68197bf296ec387b0fd diff --git a/flake.lock b/flake.lock index 501bb1506..561e97eb6 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "dank-qml-common": { "flake": false, "locked": { - "lastModified": 1790195634, - "narHash": "sha256-s+9ZHGZmKtc1oyQgvi7obdjoASEnelQ25gNO7zL3wRM=", + "lastModified": 1790208961, + "narHash": "sha256-lCXDr+VgmazTsJx7FgrBdur70tSNK2TSV/PrL+UDHsE=", "owner": "AvengeMedia", "repo": "dank-qml-common", - "rev": "640b739e31709d9aa7d5b9d26f511babc9b95daf", + "rev": "27e67d6a0cc3d6ea211fe68197bf296ec387b0fd", "type": "github" }, "original": { diff --git a/quickshell/Modules/DankDash/Media/LyricsController.qml b/quickshell/Modules/DankDash/Media/LyricsController.qml index bedd6a458..f13938c6f 100644 --- a/quickshell/Modules/DankDash/Media/LyricsController.qml +++ b/quickshell/Modules/DankDash/Media/LyricsController.qml @@ -25,6 +25,7 @@ QtObject { readonly property real position: activePlayer?.position ?? 0 readonly property int playbackState: activePlayer?.playbackState ?? MprisPlaybackState.Stopped readonly property bool playing: playbackState === MprisPlaybackState.Playing + readonly property bool stopped: playbackState === MprisPlaybackState.Stopped readonly property real rate: activePlayer?.rate ?? 1 property var lines: [] @@ -69,6 +70,7 @@ QtObject { onEnabledChanged: { if (!enabled) { cancel(); + stopClock(); if (state === "loading") { requestedKey = ""; state = "idle"; @@ -93,20 +95,22 @@ QtObject { onPositionChanged: scheduleAnchor() onRateChanged: updatePlaybackClock() onPlayingChanged: updatePlaybackClock() - onPlaybackStateChanged: { - if (playbackState === MprisPlaybackState.Stopped && !anchorPlaying) - updatePlaybackClock(); + Component.onDestruction: { + cancel(); + stopClock(); } - Component.onDestruction: cancel() function cancel() { serial++; if (requestId !== null) backend.cancelRequest(requestId); requestId = null; + requestDelay.stop(); + } + + function stopClock() { tick.stop(); anchorUpdate.stop(); - requestDelay.stop(); } function songTitle() { @@ -124,6 +128,7 @@ QtObject { } function clearContent() { + stopClock(); lines = []; plainLines = []; synced = false; @@ -188,8 +193,6 @@ QtObject { const resultKey = found ? JSON.stringify([result.instrumental === true, result.synced ?? [], result.voices ?? {}, result.plain ?? "", result.attribution ?? {}]) : ""; if (refreshing && resultKey === shownResult) return; - if (refreshing) - clearContent(); if (response.error) { useEmbeddedText("error"); return; @@ -201,6 +204,7 @@ QtObject { shownSong = requestedSong; shownResult = resultKey; if (result.instrumental) { + clearContent(); state = "instrumental"; return; } @@ -323,9 +327,9 @@ QtObject { function updatePlaybackClock() { const predicted = currentTime(); const sampled = position + (anchorPlaying ? (Date.now() - positionWall) * anchorRate / 1000 : 0); - const pendingSeek = anchorUpdate.running && Math.abs(sampled - predicted) >= DashMetrics.mediaLyricsPositionTolerance; + const pendingSeek = !stopped && anchorUpdate.running && Math.abs(sampled - predicted) >= DashMetrics.mediaLyricsPositionTolerance; anchorUpdate.stop(); - anchorPosition = playbackState === MprisPlaybackState.Stopped ? 0 : pendingSeek ? sampled : predicted; + anchorPosition = pendingSeek ? sampled : predicted; anchorWall = Date.now(); anchorPlaying = playing; anchorRate = rate; @@ -334,7 +338,7 @@ QtObject { function scheduleAnchor() { positionWall = Date.now(); - if (enabled && synced && !anchorUpdate.running) + if (enabled && synced && !stopped && !anchorUpdate.running) anchorUpdate.start(); } diff --git a/quickshell/Modules/DankDash/Media/LyricsOverlay.qml b/quickshell/Modules/DankDash/Media/LyricsOverlay.qml index 8b7164dc7..df844e734 100644 --- a/quickshell/Modules/DankDash/Media/LyricsOverlay.qml +++ b/quickshell/Modules/DankDash/Media/LyricsOverlay.qml @@ -27,6 +27,7 @@ FocusScope { readonly property int activeIndex: controller.activeIndex readonly property int firstFocusedIndex: controller.focusedGroups[0] ?? -1 readonly property string trackKey: controller.trackKey + readonly property string shownResult: controller.shownResult readonly property bool userScrolling: transcript.isUserScrolling || transcript.dragging readonly property real currentLineY: transcript.currentItem?.y ?? 0 readonly property real shapeInset: Math.ceil(Math.min(radius, width / 2, height / 2) * (1 - Math.SQRT1_2)) @@ -50,6 +51,7 @@ FocusScope { onActiveIndexChanged: followTimer.restart() onFirstFocusedIndexChanged: followTimer.restart() onReadyChanged: snapToCurrent() + onShownResultChanged: snapToCurrent() onWidthChanged: snapToCurrent() onHeightChanged: snapToCurrent() onTrackKeyChanged: { diff --git a/quickshell/Modules/DankDash/MediaPlayerTab.qml b/quickshell/Modules/DankDash/MediaPlayerTab.qml index eece27944..9781b2839 100644 --- a/quickshell/Modules/DankDash/MediaPlayerTab.qml +++ b/quickshell/Modules/DankDash/MediaPlayerTab.qml @@ -100,10 +100,10 @@ Item { if (!root.live || !root.lyricsOpener?.visible || !root.lyricsOpener.enabled) return; if (typeof root.lyricsOpener.requestFocus === "function") { - root.lyricsOpener.requestFocus(false); + root.lyricsOpener.requestFocus(false, Qt.OtherFocusReason); return; } - root.lyricsOpener.forceActiveFocus(Qt.PopupFocusReason); + root.lyricsOpener.forceActiveFocus(Qt.OtherFocusReason); } } diff --git a/quickshell/Modules/DankIsland/Activities/DashTabFace.qml b/quickshell/Modules/DankIsland/Activities/DashTabFace.qml index 85bfd72e3..a71cc29f3 100644 --- a/quickshell/Modules/DankIsland/Activities/DashTabFace.qml +++ b/quickshell/Modules/DankIsland/Activities/DashTabFace.qml @@ -78,7 +78,12 @@ FocusScope { function focusFace() { pageActions.clearFocus(); - (root.tab?.focusTarget ?? root).forceActiveFocus(); + const target = root.tab?.focusTarget ?? root; + if (typeof target.requestFocus === "function") { + target.requestFocus(false, Qt.OtherFocusReason); + return true; + } + target.forceActiveFocus(Qt.OtherFocusReason); return true; } diff --git a/quickshell/tests/qml/media-lyrics.qml b/quickshell/tests/qml/media-lyrics.qml index 521c46aa4..bcf4a1f9c 100644 --- a/quickshell/tests/qml/media-lyrics.qml +++ b/quickshell/tests/qml/media-lyrics.qml @@ -206,6 +206,20 @@ ShellRoot { check(!overlay.following && input.findChild(overlay, "followPlayback")?.visible, "browsing pauses automatic following"); input.findChild(overlay, "followPlayback").click(); check(overlay.following, "follow action resumes synchronized scrolling"); + const settledCalls = backend.calls; + MprisController.stableAlbum = "Late album"; + waitFor(() => backend.calls === settledCalls + 1, "a late album refreshes the lookup"); + check(media.lyrics.state === "ready" && media.lyrics.tick.running, "the shown lyrics keep following during the refresh"); + backend.respond({ + result: { + found: true, + synced: root.timed + } + }); + check(media.lyrics.tick.running, "an unchanged refresh keeps the timer running"); + source.playbackState = MprisPlaybackState.Stopped; + check(media.lyrics.activeIndex === 1, "stopped playback holds the lyric position"); + source.playbackState = MprisPlaybackState.Playing; source.playbackState = MprisPlaybackState.Paused; check(!media.lyrics.tick.running, "paused playback has no lyric timer"); source.playbackState = MprisPlaybackState.Playing; @@ -229,7 +243,7 @@ ShellRoot { check(!media.lyrics.tick.running, "closing stops scheduling"); media.lyricsOpen = true; waitFor(() => !!media.lyricsFocusTarget, "reopened lyrics load"); - check(backend.calls === 1 && media.lyrics.state === "ready", "reopening reuses loaded lyrics"); + check(backend.calls === settledCalls + 1 && media.lyrics.state === "ready", "reopening reuses loaded lyrics"); media.lyrics.request(); const stale = backend.pending[backend.nextId]; MprisController.stableTitle = "Next track";