| 1 | pragma Singleton |
| 2 | import QtQuick |
| 3 | import Quickshell |
| 4 | import Quickshell.Io |
| 5 | import Quickshell.Services.Mpris |
| 6 | |
| 7 | // Audio spectrum from cava (raw ascii output on stdout). Only runs while an MPRIS player is |
| 8 | // playing and something wants it (`wanted`), so it costs nothing when idle. Hidden if cava is missing. |
| 9 | Singleton { |
| 10 | id: root |
| 11 | readonly property int bars: Math.max(8, Settings.vizBars) |
| 12 | readonly property bool wanted: Settings.desktopViz |
| 13 | property bool available: false |
| 14 | property var values: [] // 0..1 per bar |
| 15 | |
| 16 | function anyPlaying() { |
| 17 | var l = Mpris.players.values |
| 18 | for (var i = 0; i < l.length; i++) if (l[i].isPlaying) return true |
| 19 | return false |
| 20 | } |
| 21 | readonly property bool playing: anyPlaying() |
| 22 | readonly property bool active: available && wanted && playing |
| 23 | onActiveChanged: if (!active) idle.restart() |
| 24 | |
| 25 | readonly property string conf: |
| 26 | "[general]\nbars = " + bars + "\nframerate = 60\nsleep_timer = 3\n" + |
| 27 | "[input]\nmethod = pipewire\nsource = auto\n" + |
| 28 | "[output]\nmethod = raw\nraw_target = /dev/stdout\nchannels = mono\n" + |
| 29 | "data_format = ascii\nascii_max_range = 1000\nbar_delimiter = 59\nframe_delimiter = 10\n" + |
| 30 | "[smoothing]\nnoise_reduction = 70\n" |
| 31 | |
| 32 | Process { |
| 33 | command: ["sh", "-c", "command -v cava"] |
| 34 | running: true |
| 35 | onExited: code => root.available = code === 0 |
| 36 | } |
| 37 | |
| 38 | Process { |
| 39 | id: proc |
| 40 | running: root.active |
| 41 | command: ["sh", "-c", 'mkdir -p "${2%/*}" && printf "%s" "$1" > "$2" && exec cava -p "$2"', |
| 42 | "sh", root.conf, Quickshell.env("HOME") + "/.cache/aurora/cava.conf"] |
| 43 | stdout: SplitParser { |
| 44 | onRead: line => { |
| 45 | var p = line.split(";"), out = [] |
| 46 | for (var i = 0; i < p.length; i++) if (p[i] !== "") out.push(parseInt(p[i]) / 1000) |
| 47 | root.values = out |
| 48 | } |
| 49 | } |
| 50 | } |
| 51 | // let the bars fall to zero once cava stops instead of freezing mid-frame |
| 52 | Timer { id: idle; interval: 50; onTriggered: root.values = [] } |
| 53 | } |