Replace QML CAVA polling with SSE server for smooth bars

DataSource fork+exec was limited to ~1fps. Now cava pipes into a Python
SSE server (port 5555) and the HTML connects directly via EventSource,
getting updates at up to 60fps without QML overhead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ltadeu6 2026-06-07 09:37:58 -03:00
parent 5876701a64
commit 39e7c07309
No known key found for this signature in database
GPG key ID: FB9FDAB6B6F3418D
4 changed files with 59 additions and 22 deletions

View file

@ -0,0 +1,45 @@
#!/usr/bin/env python3
"""
Reads CAVA ASCII output from stdin and streams it to the wallpaper
via Server-Sent Events on http://127.0.0.1:5555.
Run as: cava -p cava.conf | python3 cava-server.py
"""
import sys
import threading
import time
from http.server import HTTPServer, BaseHTTPRequestHandler
latest = ""
lock = threading.Lock()
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-Type", "text/event-stream")
self.send_header("Cache-Control", "no-cache")
self.send_header("Access-Control-Allow-Origin", "*")
self.end_headers()
last = None
try:
while True:
with lock:
cur = latest
if cur and cur != last:
self.wfile.write(f"data: {cur}\n\n".encode())
self.wfile.flush()
last = cur
time.sleep(0.016) # cap at ~60fps
except Exception:
pass
def log_message(self, *args):
pass
def serve():
HTTPServer(("127.0.0.1", 5555), Handler).serve_forever()
threading.Thread(target=serve, daemon=True).start()
for line in sys.stdin:
with lock:
latest = line.strip()

View file

@ -1,9 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Kill any previous instance tied to this config
pkill -f "cava -p.*skeledance" 2>/dev/null pkill -f "cava -p.*skeledance" 2>/dev/null
pkill -f "cava-server.py" 2>/dev/null
sleep 0.2 sleep 0.2
CONF="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/cava.conf" DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cava -p "$CONF" | while IFS= read -r line; do cava -p "$DIR/cava.conf" | python3 "$DIR/cava-server.py"
printf '%s\n' "$line" > /tmp/skeledance_bars
done

View file

@ -886,7 +886,7 @@ for (let i = 0; i < VU_COUNT; i++) {
vuBars.push(bar); vuBars.push(bar);
} }
// Called by QML with semicolon-separated CAVA values (0100 per bar) // Called with semicolon-separated CAVA values (0100 per bar)
function updateBars(raw) { function updateBars(raw) {
if (!isPlaying) return; if (!isPlaying) return;
const vals = raw.split(';'); const vals = raw.split(';');
@ -898,6 +898,13 @@ function updateBars(raw) {
}); });
} }
// Connect to CAVA SSE server (started by cava-start.sh via QML)
(function connectCava() {
const sse = new EventSource('http://127.0.0.1:5555');
sse.onmessage = e => { if (e.data) updateBars(e.data); };
sse.onerror = () => { sse.close(); setTimeout(connectCava, 3000); };
})();
/* ============================================================ /* ============================================================
FLOATING MUSIC NOTES FLOATING MUSIC NOTES
============================================================ */ ============================================================ */

View file

@ -16,6 +16,7 @@ WallpaperItem {
settings { settings {
javascriptEnabled: true javascriptEnabled: true
localContentCanAccessFileUrls: true localContentCanAccessFileUrls: true
localContentCanAccessRemoteUrls: true
localStorageEnabled: false localStorageEnabled: false
} }
@ -51,18 +52,4 @@ WallpaperItem {
} }
} }
// Polls CAVA bars at ~60fps
Plasma5Support.DataSource {
id: cavaSource
engine: "executable"
connectedSources: ["cat /tmp/skeledance_bars 2>/dev/null || echo ''"]
interval: 80
onNewData: function(sourceName, data) {
var raw = data["stdout"].trim()
if (raw !== "") {
webView.runJavaScript("updateBars(" + JSON.stringify(raw) + ")")
}
}
}
} }