diff --git a/README.md b/README.md index 219398d..e6d9360 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ You poor thing. Here's what the Nix packaging does for you, manually: 1. Build [quickshell](https://quickshell.outfoxxed.me) from source. You'll need Qt 6. 2. Build `nova-stats` from `stats-daemon/` (`cargo build --release`). Put the binary in your PATH. 3. Put `gdbus` (from glib) in your PATH. The lock screen needs it. -4. Compile the overview shader: `qsb --qt6 -o shell/modules/hex_wave.frag.qsb shell/modules/hex_wave.frag` +4. Compile the shaders: `for f in shell/modules/*.frag; do qsb --qt6 -o "${f}.qsb" "$f"; done` 5. Install [Symbols Nerd Font](https://www.nerdfonts.com/). 6. Create `~/.config/nova-shell/theme.json` and `~/.config/nova-shell/modules.json`. The Nix module generates these from your config. Without it, you write JSON by hand like a human. See the theme and module tables below for keys. 7. Run: `quickshell -p /path/to/nova-shell/shell/shell.qml` diff --git a/shell/services/MprisService.qml b/shell/services/MprisService.qml index 9f06ead..bf3a61f 100644 --- a/shell/services/MprisService.qml +++ b/shell/services/MprisService.qml @@ -11,11 +11,149 @@ QtObject { readonly property MprisPlayer player: players[playerIdx] ?? players[0] ?? null readonly property bool playing: player?.playbackState === MprisPlaybackState.Playing - // Reset index if current player disappears - onPlayersChanged: if (playerIdx >= players.length) - playerIdx = 0 + // Track the identity of the selected player so we can find it after list changes + property string _selectedIdentity: "" + + // Track which player played most recently (by identity) + property var _lastPlayedTime: ({}) + + onPlayerChanged: { + if (player) + _selectedIdentity = player.identity ?? ""; + } + + onPlayersChanged: { + // Try to find the previously selected player by identity + if (_selectedIdentity) { + const idx = players.findIndex(p => (p.identity ?? "") === _selectedIdentity); + if (idx >= 0) { + playerIdx = idx; + return; + } + } + + // Selected player disappeared - switch to most recently playing + _switchToBest(); + } + + // Watch playback state changes on all players + property Connections _mprisConn: Connections { + target: Mpris + + function onPlayersChanged() { + // Re-evaluate when the global player list changes + Qt.callLater(_reconnectWatchers); + } + } + + property var _watchedPlayers: [] + + function _reconnectWatchers() { + // Clean up old watchers + for (const w of _watchedPlayers) { + if (w.conn) + w.conn.destroy(); + } + _watchedPlayers = []; + + // Watch each player's playback state + for (const p of players) { + const conn = _watcherComp.createObject(root, { + target: p + }); + _watchedPlayers.push({ + player: p, + conn: conn + }); + } + } + + property Component _watcherComp: Component { + Connections { + function onPlaybackStateChanged() { + root._onPlaybackStateChanged(target); + } + } + } + + function _onPlaybackStateChanged(p) { + if (!p) + return; + + const identity = p.identity ?? ""; + + if (p.playbackState === MprisPlaybackState.Playing) { + // Record this as the most recently playing + const times = Object.assign({}, _lastPlayedTime); + times[identity] = Date.now(); + _lastPlayedTime = times; + + // If current player is not playing, switch to this one + if (player !== p && player?.playbackState !== MprisPlaybackState.Playing) { + const idx = players.indexOf(p); + if (idx >= 0) { + playerIdx = idx; + _selectedIdentity = identity; + } + } + } else if (p.playbackState === MprisPlaybackState.Paused && p === player) { + // Current player paused - switch to a playing one if available + _switchToPlaying(); + } + } + + function _switchToPlaying() { + // Find a playing player, prefer most recently started + let best = -1; + let bestTime = 0; + for (let i = 0; i < players.length; i++) { + if (players[i].playbackState === MprisPlaybackState.Playing) { + const t = _lastPlayedTime[players[i].identity ?? ""] || 0; + if (best < 0 || t > bestTime) { + best = i; + bestTime = t; + } + } + } + if (best >= 0) { + playerIdx = best; + _selectedIdentity = players[best].identity ?? ""; + } + } + + function _switchToBest() { + // Switch to playing player, or most recently played + let best = -1; + let bestTime = 0; + let bestPlaying = -1; + let bestPlayingTime = 0; + + for (let i = 0; i < players.length; i++) { + const identity = players[i].identity ?? ""; + const t = _lastPlayedTime[identity] || 0; + if (players[i].playbackState === MprisPlaybackState.Playing) { + if (bestPlaying < 0 || t > bestPlayingTime) { + bestPlaying = i; + bestPlayingTime = t; + } + } + if (best < 0 || t > bestTime) { + best = i; + bestTime = t; + } + } + + const chosen = bestPlaying >= 0 ? bestPlaying : (best >= 0 ? best : 0); + playerIdx = chosen; + if (players[chosen]) + _selectedIdentity = players[chosen].identity ?? ""; + } function switchPlayer(idx) { playerIdx = idx; + if (players[idx]) + _selectedIdentity = players[idx].identity ?? ""; } + + Component.onCompleted: Qt.callLater(_reconnectWatchers) }