diff --git a/Startup/deltarune.mp4 b/Startup/deltarune.mp4 new file mode 100644 index 0000000..d2c55d1 Binary files /dev/null and b/Startup/deltarune.mp4 differ diff --git a/Startup/deltarune.webm b/Startup/deltarune.webm deleted file mode 100644 index c77a1c1..0000000 Binary files a/Startup/deltarune.webm and /dev/null differ diff --git a/Startup/shell.qml b/Startup/shell.qml index a64a26c..72b2cee 100644 --- a/Startup/shell.qml +++ b/Startup/shell.qml @@ -1,5 +1,7 @@ import Quickshell import Quickshell.Bluetooth +import Quickshell.Io +import Quickshell.Services.Mpris import Quickshell.Wayland import Quickshell.Hyprland import QtQuick @@ -29,6 +31,11 @@ PanelWindow { property bool waitingForBluetooth: true property bool animationLaunchScheduled: false property bool animationStarted: false + property bool isEnding: false + property bool postIntroSongTriggered: false + property bool waitingForPostIntroSong: false + property bool waitingForChromiumSong: false + property bool finishDelayScheduled: false property bool bluetoothConnectRequested: false property int bluetoothTargetAttemptIndex: 0 property string bluetoothActiveTargetName: "" @@ -146,6 +153,67 @@ PanelWindow { Qt.quit(); } + function triggerPostIntroSong() { + if (postIntroSongTriggered) + return; + postIntroSongTriggered = true; + isEnding = true; + waitingForPostIntroSong = true; + waitingForChromiumSong = true; + console.log("Startup: Triggering post-intro song..."); + postIntroSongTimeout.restart(); + chromiumSongTimeout.restart(); + postIntroSongProcess.exec(["curl", "-s", "-X", "POST", "-H", "Content-Type: application/json", "-d", "{\"type\":\"songs\",\"id\":\"i.WmYR0zXC68B2g50\"}", "http://localhost:10767/api/v1/playback/play-item"]); + } + + function chromiumSongReady() { + for (var i = 0; i < mprisPlayerRepeater.count; i++) { + var item = mprisPlayerRepeater.itemAt(i); + if (!item || !item.player) + continue; + + var player = item.player; + var identity = String(player.identity || "").toLowerCase(); + var desktopEntry = String(player.desktopEntry || "").toLowerCase(); + var dbusName = String(player.dbusName || "").toLowerCase(); + var isChromium = identity.indexOf("chromium") !== -1 || desktopEntry.indexOf("chromium") !== -1 || dbusName.indexOf("chromium") !== -1; + + if (!isChromium) + continue; + + var album = String(player.trackAlbum || ""); + var artist = String(player.trackArtist || ""); + var title = String(player.trackTitle || ""); + + console.log("Startup: chromium metadata", identity, album, artist, title); + + if (album === "Deltarune Chapters 3+4 (Original Game Soundtrack)" && artist === "Toby Fox" && title === "With Hope Crossed on Our Hearts") + return true; + } + + return false; + } + + function maybeFinishAfterSongReady() { + if (!isEnding) + return; + if (waitingForPostIntroSong) + return; + + if (chromiumSongReady()) { + console.log("Startup: chromium MPRIS metadata matched, waiting 1s before closing overlay"); + waitingForChromiumSong = false; + chromiumSongTimeout.stop(); + if (!finishDelayScheduled) { + finishDelayScheduled = true; + finishDelayTimer.restart(); + } + return; + } + + console.log("Startup: waiting for chromium MPRIS metadata"); + } + HyprlandFocusGrab { active: true windows: [overlay] @@ -260,17 +328,126 @@ PanelWindow { } } + Repeater { + id: mprisPlayerRepeater + model: Mpris.players + + delegate: Item { + required property var modelData + property var player: modelData + visible: false + + Connections { + target: player + function onTrackChanged() { + overlay.maybeFinishAfterSongReady(); + } + function onPostTrackChanged() { + overlay.maybeFinishAfterSongReady(); + } + function onMetadataChanged() { + overlay.maybeFinishAfterSongReady(); + } + function onTrackTitleChanged() { + overlay.maybeFinishAfterSongReady(); + } + function onTrackArtistChanged() { + overlay.maybeFinishAfterSongReady(); + } + function onTrackAlbumChanged() { + overlay.maybeFinishAfterSongReady(); + } + function onIdentityChanged() { + overlay.maybeFinishAfterSongReady(); + } + function onDesktopEntryChanged() { + overlay.maybeFinishAfterSongReady(); + } + } + } + } + + Connections { + target: Mpris.players + ignoreUnknownSignals: true + function onCountChanged() { + overlay.maybeFinishAfterSongReady(); + } + function onModelReset() { + overlay.maybeFinishAfterSongReady(); + } + function onRowsInserted() { + overlay.maybeFinishAfterSongReady(); + } + function onRowsRemoved() { + overlay.maybeFinishAfterSongReady(); + } + } + MediaPlayer { id: player - source: "deltarune.webm" // Local file or URL + source: "deltarune.mp4" autoPlay: false videoOutput: videoOutput playbackRate: 1 audioOutput: startupAudioOutput - onPlaybackStateChanged: a => { - console.log("Startup: playback state changed", player.playbackState); - if (player.playbackState === MediaPlayer.StoppedState) + onMediaStatusChanged: { + console.log("Startup: Media Status:", player.mediaStatus); + if (player.mediaStatus === MediaPlayer.EndOfMedia) + overlay.triggerPostIntroSong(); + } + onPlaybackStateChanged: { + if (player.playbackState === MediaPlayer.StoppedState && !overlay.isEnding) { + console.log("Startup: Video stopped unexpectedly, shutting down."); overlay.finishStartup(); + } + } + } + + Process { + id: postIntroSongProcess + onExited: (exitCode, exitStatus) => { + console.log("Startup: post-intro song request exited", exitCode, exitStatus); + postIntroSongTimeout.stop(); + overlay.waitingForPostIntroSong = false; + overlay.maybeFinishAfterSongReady(); + } + } + + Timer { + id: postIntroSongTimeout + interval: 3000 + repeat: false + onTriggered: { + console.log("Startup: post-intro song request timed out"); + if (overlay.waitingForPostIntroSong) { + postIntroSongProcess.running = false; + overlay.waitingForPostIntroSong = false; + overlay.maybeFinishAfterSongReady(); + } + } + } + + Timer { + id: chromiumSongTimeout + interval: 8000 + repeat: false + onTriggered: { + console.log("Startup: chromium MPRIS metadata wait timed out"); + if (overlay.waitingForChromiumSong) { + overlay.waitingForChromiumSong = false; + overlay.finishStartup(); + } + } + } + + Timer { + id: finishDelayTimer + interval: 1000 + repeat: false + onTriggered: { + overlay.finishDelayScheduled = false; + overlay.finishStartup(); } } @@ -282,6 +459,8 @@ PanelWindow { console.log("Startup: startup delay elapsed, playing video"); overlay.animationLaunchScheduled = false; overlay.animationStarted = true; + overlay.isEnding = false; + overlay.finishDelayScheduled = false; player.play(); } }