docs(release): prepare 2.1.0 notes and packaging

cx
This commit is contained in:
vicrodh committed 2026-09-01 20:12:53 -06:00
1 parent db1ac051f2
commit 7627ac7a36
6 files changed
+619 -38

No files matched your search

+367 -30
View File
@@ -1,33 +1,370 @@
# QBZ 2.1.0 — DRAFT (notas por escribir)
# QBZ 2.1.0 — Technical Changelog (v2.0.2 → v2.1.0)
> **TO-DO para el primer release con frontend Qt.** Este archivo es un
> placeholder abierto 2026-08-13 (km) para que la campaña de GPU no se
> pierda al redactar las notas. Rellenar el resto del changelog en el
> release. Referencia completa:
> `qbz-nix-docs/qt-frontend/2026-08-11-scenegraph-batches/GPU-COST-INVESTIGATION.md`
~750 commits on `pre-release` (2026-07 → 2026-08). This release replaces the
Slint frontend shipped in 2.0.x with a native Qt/QML frontend (`qbz-qt`,
cxx-qt bridges) running in the same single Rust process, and ships the first
Windows build. The frontend-agnostic core crates carry over unchanged in
architecture; the protected audio path (ALSA direct / PipeWire / Pulse /
JACK, bit-perfect) was not modified by the frontend swap. Sections group work
by area rather than by commit date.
## Qt frontend — el costo de GPU del shell (95 % → 25 %)
---
- El shell (fondo dinámico + visualizador del NPB Large) pasó de **93-97 %
de GPU / ~34 W a 25-26 % / ~13 W**, medido en la laptop del owner —
mejor que la referencia Slint (35-56 %) en la misma máquina. La causa
raíz era la tasa de presents de ventana completa, no el render: Qt Quick
no tiene repintado parcial, y en el stack híbrido (KWin componiendo en la
dGPU) cada present cuesta ~1.2 % de GPU fijo, sin importar el área.
- **Un solo reloj de repintado para todo el shell** (`QbzShell.pulseMs`,
~30 Hz, knob `QBZ_PULSE_MS`): la atmósfera del fondo, el visualizador y
el motor de lyrics tickan en el mismo flanco. Regla permanente: ninguna
animación continua usa `Timer`/`NumberAnimation`/`Behavior` propio, y un
componente invisible o congelado no escribe nada.
- Fugas cerradas: el FBO del campo ambient double-presentaba; los paneles
immersive (montados aunque el overlay esté cerrado) escribían en cada
publish del FFT; los Behaviors de 100 ms del panel reactivo animaban a
tasa de display (immersive: 83 % → ~25 %).
- El fondo modo 2 (blurred) compone sus cuatro capas + dim + scrim en **un
solo ShaderEffect opaco** (paridad numérica verificada); el stack de
imágenes queda como fallback para el renderer de software.
- **Regresa el indicador animado de now-playing en las filas de tracks**
(eq bars en la celda de play), detrás del pref
`play-indicator-animation` — ahora montado en el pulso, cuesta cero
presents extra. Con el pref apagado se mantiene la pill de acento
estática.
## 1. Architecture
- **Slint frontend retired** (`69ed61207`): the `qbz-ui`, `qbz`,
`qbz-slint-common`, `qbz-dac-wizard` and `vendor` crates left the live
workspace. The DAC wizard's headless logic survives as
`qbz-dac-wizard-core`. With the ~1.6 M-line generated Slint module gone,
the 20–30 GB compile wall is history — a full release build now takes
minutes on standard runners.
- **New frontend crate `qbz-qt`**: Qt Quick/QML UI over cxx-qt bridges,
built up in phases 0–23 (`469f83781` … `9a4fec6b7`) from login shell to
full parity, then split out of the initial God-object into per-domain
bridges (`ade156a72`, `5c2577c7c`, `fa433d465`).
- **`qbz-source` — the source-agnostic media seam** (`f1b424134`): one
registry that resolves every play/queue/artwork request by provenance
(Qobuz, local, offline cache, Plex, Jellyfin, Subsonic, disc), with
`PlaybackTicket` as the single audible step (`a55e42633`, `7113ef579`).
My QBZ and mixtapes were rewired through the seam (`b6cf06ff2`,
`d9ed19eb6`).
- **New crates this cycle**: `qbz-jellyfin` (`e578d8a54`), `qbz-subsonic`
(`0f41c44da`), `qbz-media-cache` (`0dfec607c`), `qbz-local-catalog`
(`a0d8a7fc9`), `qbz-disc` (`db0999bf6`), `qbz-rip` (`5d3d51a88`),
`qconnect-lan` (LAN discovery/pairing service, merged in `28fe63514`).
## 2. Qt frontend port
- Full parity rebuild against the Slint reference: shell, discover/home,
library, album/artist pages, settings 1:1 (`83999fc55`), playlists and
My QBZ (`a16cf56c8`, `acf23abc9`), blacklist manager (`494596616`),
purchases (`7d9d3b896`-era, `475f261dc`, `7d98ad044`), awards pages
(`dc2496e5c`), HiFi Wizard (`8ba948346`), immersive mode (B1–B5,
`b5298b78b` … `c0f4d2f1b`), search cortinilla with local sections
(`15610bc7e`, `a7e64c0bd`), hotkeys with a cheatsheet and customize editor
(`0cca9abc5` … `07b0ba818`).
- **Miniplayer and tray**: borderless mini window with queue and lyrics
surfaces (`cd89b4094`, `684fffd7f`), system tray via ksni (`56d438717`),
close-to-tray on every exit path (`088387f2c`), window position restored
after un-hiding (`f64b8480d`).
- **MPRIS**: the desktop sees the player again (`5dd450fb9`); `Position` is
computed at read time and `Seeked` is emitted, so external widgets stop
extrapolating a frozen position (`f45b703c6`, `7ba07ec6f`, `8bc32a91a`).
- **Session persistence**: view triple, window geometry, playback state and
per-page scroll positions restored across restarts (`c2944db97`,
`89c54cd5b`, `e816436fd`, `221572b36`).
- **Kiosk profile**: a zone-navigated full-screen shell with its own NavRail,
card primitives and eight views, plus a live toggle (`87c20fce8`,
`221f8704d`, `63e36c754`).
- **i18n**: live language switching across the 8 bundled locales via the
`trRev` dependency pattern (`245eaa4d4`); `qbz-i18n` owns its catalogues
(`1352e499f`).
- QoL: Excel-style Shift multi-select on every surface (`750c59034`),
an app-wide typeface setting (`3c4c2dae2`), system-font script fallback
(`82953c72d`), artwork fade-in on arrival (`58fa77a39`), desktop
notifications only where a toast can arrive (`27022fbf4`) with fresh
portal ids per track on Plasma (`f6574cdb9`).
## 3. Windows port (PR #725)
- First-class Windows build: WiX MSI packaging with an install/launch/
uninstall smoke in CI (`8fb02401f`, `783dbb964`, `9a0dbd60c`), custom
title bar with a native hit test (`415cba57d`), single instance and
`qbz://` protocol deep links (`afa32d9bb`), session persisted when
Windows ends it (`3b032e8c4`), miniplayer keep-on-top (`c2272ad9f`), an
as-is disclaimer shown once per version (`2e0e29277`).
- **WASAPI exclusive-mode** sample-rate probe and hotplug watch
(`c068b2fd6`).
## 4. Local Library, catalog & media servers
- **Derived local catalog** (`qbz-local-catalog`): Albums, Artists and
Tracks page from a reconciled per-source catalog (`1bb3c41dc`,
`299dcde5b`, `6fd83c42d`, `7689fb0df`), with incremental local scans
(`480b4b46d`), incremental Plex section sync (`6e0e0d316`),
generation-tagged Subsonic sync (`6005510bf`), background Jellyfin quality
hydration (`8fff37a8e`), recovery guardrails (`ed8d11e5c`) and album-version
unification across sources (`d627560f4`).
- **Jellyfin and Subsonic integrations**: protocol crates verified against
live servers (`e578d8a54`, `0f41c44da` — the Subsonic HTTP-200 error trap
is enforced in code), `JellyfinSource`/`SubsonicSource` through the seam
with the full chain walked against both real servers (`b09f03b73`,
`51aa8ba08`), one shared media cache table (`0dfec607c`), filter chips in
Local Library (`322b9374f`), merged into both local search paths
(`13e938727`). Playback from both verified bit-perfect by checksum.
- **Library Explorer** (`9c22a909d`): tree-style browsing across every
source, with drag-and-drop of rows into sidebar playlists and the queue,
a collapse-caret tooltip and a collapsed filter strip (`c37768459`).
- **Local tag editor** restored and expanded: split editor workspace,
canonical write verification, remote metadata lookup (`0e5383758`,
`75a668ed1`, `d81f69113`), scoped track-row selection (`fa649394d`),
completed editor sessions (`5a14eef25`).
- **Multi-disc albums**: a divider that names and illustrates each disc
(`fd25593e3`); SACD image tracks persist in the library (`23a4eb291`).
- **Favorites freshness** (#690): favoriting an album shows up in Library
without a restart (`bb1b1a564`).
- perf: covers no longer drag their embedded artwork through `lofty` just to
read tags (`34d19211d`, 9.3 s → 0.9 s on a large scan), high-cardinality
views windowed and virtualized (`e0e9566f8`, `c6d142ccb`), Tracks tab
pages append without a model reset and keep the viewport (`fcb985173`,
`f8092db81`).
## 5. Discs: CD, SACD and ripping
- **CD-DA playback** off a real drive, measured before it was written
(`db0999bf6`), with a platform-split reader for macOS (`8d4ed8e95`); a CD
track is a source ticket of its own, not a file (`3ba476b43`).
- **SACD images**: the stereo area reads and plays (`06405969e`,
`452589dda`); the variable per-sector header and the byte-interleaved
stereo layout are handled correctly (`70defb2e8`).
- **Ripping** (`qbz-rip`): CD → tagged FLAC, bit-exact and seekable
(`5d3d51a88`), with a rip wizard, metadata correction pane (`2273db918`),
remote album metadata search (`b6deec0aa`), a provenance log and cover
beside the tracks (`b69a058bc`), and disc memory after eject
(`57ae46566`).
## 6. Playback & audio
- **ALSA hardware volume** (new, guarded — #726 hardening): capability-probed
enumeration of simple-mixer elements with typed identities
`(name, index)`, per-device+route persistence keyed by udev identity,
UCM read-only hints, a closed selector when more than one candidate is
valid, and fail-closed behavior on ambiguity or disappearance
(`0e8614fe3`, `96342e23b`, `c3556fe3e`, merged `c059c12bf`). Exposed in
Qt Settings and the qbzd CLI/TUI. The mixer opens on the card ctl id, not
the PCM alias (#659, `def93528b`).
- **Playback recovery**: per-attempt total deadlines, transient retries and
monotonic track-identity signals for source failures; the queue advances
even if polling missed the stopped pulse — an unreachable track can no
longer stop playback (`99722aeda`, merged `bb43dea5b`); audible state is
preserved on source failures (`ecae778d5`).
- **Stereo scope visualizers** (`d11e94622`), with zero-frame packet
skipping (`6046a959c`). (The seekbar-waveform experiment from the same
round ended up parked and hidden — `a92dafa73` — and is deliberately not
listed as a feature.)
- **ALSA Direct visualizer alignment**: ~10 ms pacing aligned to the
playhead via `snd_pcm_delay` (`eff9c484e`).
- **CMAF**: the superseded track's segment feeder is cancelled
(`a561e7ae2`), stale gapless pending state cleared on PlayStreaming
(`740adf55d`), fetch timing split into send vs body (`63b4082e6`).
- **DSD**: conversion cancelled when its track is superseded (`a0ed0d752`);
`AudioFormat::Dsd` round-trips through the database instead of decaying
to Unknown (`6328994c0`); the quality badge classifier learned DSD
(`71f8d0897`).
- **Listen log** (merged `a3161254e`): a per-user listening event store and
tracker with reading rules, a Listening history toggle and clear in
Settings, cross-source identity capture (ISRC / MusicBrainz recording ids
from tags, Plex, Jellyfin and Subsonic), and scrobble start-stamping timed
by audible position. A gapless hand-off closes the previous row as
natural, not skip (`7b05ef55a`). Groundwork for offline recommendations.
- **Pulled tracks**: tracks Qobuz removed are marked and kept out of the
queue (`7780efa5d`), with an available-version finder (`b2c74c414`) and a
menu that offers nothing leading nowhere (`e779b6ec1`).
- Offline and logged-out scrobbling restored (`aadad1da5`); player volume
persists across launches (`b8b493bd5`).
## 7. Qobuz Connect
- **LAN parity** (merged `28fe63514`): the official-client LAN service —
HTTP + mDNS announce, delegated credentials kept isolated, a transactional
session coordinator — so official Qobuz apps discover and pair with QBZ on
the local network, always-on with the global QConnect switch, exactly like
the official clients. The wire contract was normalized from real captures
(APK/Electron/web bundle).
- **Hardening**: real buffering states (`PLAYING+BUFFERING` intent
preserved), duration/offset corrections, authority fences and bounded
teardown for Qt and qbzd, last-wins takeover, and exact Cast↔QConnect
transfer with synchronous epochs. Shuffle materializes the WS seed exactly
(xoshiro128** + official Fisher-Yates), remote volume/mute project onto
the now-playing state, and ceding to another renderer waits for
`ACTIVE_RENDERER_CHANGED` before stopping. Race-sensitive logic lives once
in `qconnect-app`, not per-frontend.
- Verified in smokes against the official iOS client and Web Player:
identical queue order on all three, volume both ways, handoff without
pausing the session.
## 8. Casting
- **Round two** (merged `44c108d4d`): progressive serving, clicked-track
routing, a source proxy so Plex/Jellyfin/Subsonic tracks cast too, and a
clean shutdown path.
- **Single remote-renderer seam**: `route_play_remote` (cast → peer → local)
behind every play entry point, a typed `NoMediaSession` idle poll and a
volume coalescer (merged `95f2d6519`).
- **Visualizer while casting**: a shadow decoder feeds the scopes/spectrum
for proxied and direct tracks while audio renders on the cast device
(merged `3fc23aea4`, `41b8e7e6f` — the latter also fixes DLNA
stop-before-load).
- **X.509 v1 device certificates** accepted in the Cast TLS handshake
(#730, merged `e9ba7a7bf`): `rust_cast` vendored with a 38-line delta,
verified byte-for-byte against crates.io — older Chromecast built-in
devices work again.
## 9. Playlists & queue
- **Add-to-playlist redesign** (merged `4c4647bdf`): a membership index with
a background hydrator, a picker showing "Already in" and the last-used
playlist, local drag-and-drop, Jellyfin/Subsonic tracks in playlists, and
quality badges with tooltips.
- **Album Quick View** and queue focus controls (`d555b8fde`).
- **Play later, everywhere** (merged `ea02cc238`): album-level enqueue takes
the block-tail arm (#442) so "later" means after the current block;
"Play all later" on the Popular Tracks group.
- **Extended queue view** (`c7038a685`), drag-and-drop insert at position
(`c198afb33`), reorderable history occurrences (`4630c4022`), playback
history persisted across restarts (`a2d0ada42`).
- **Importer expansion**: playlist files, JSON, ListenBrainz and Last.fm as
import sources (`7e233104f`), reachable from the Playlist Manager toolbar
(`9899ad6b0`).
- Mixed playlists open offline (`b4744b6e7`); playlist cover editing
restored (`aa66c58ea`); custom covers propagate to every surface
(`d112c726b`).
## 10. Search & cortinilla
- Local sections in the search cortinilla with click routing into the
LocalLibrary tabs (`15610bc7e`, `4806584ee`), instant cached first paint
(`b38b0b3d5`), module-OFF results fallback
(`10f249be2`), debounce moved from QML into Rust (`5a194d249`).
- Focus-driven lifetime instead of idle timers, a Cut/Copy/Paste/Select-all
menu on the search input, and "Open containing album" on track rows
(merged `ea02cc238`).
- Tracks results use the app's full track row (`7fc760588`); section rows
can open their first tab on click, opt-in (`e9f09a59a`).
## 11. Immersive & visualizers
- Immersive port and expansion: root overlay and header band (`8bc255269`),
atmosphere and focus panels (`46c1c4240`), split view with lyrics, track
info, suggestions and queue tabs (`0b6e8ed7c`), player bar and in-immersive
search cortinilla (`c0f4d2f1b`), shader scenes with RHI items and GL link
pairing (`e6c058c2f`), artist scene with the shared scene-discovery engine
(`016eb3cd2`), remembered shader scene (`47cab502f`).
- Visual cleanup round (merged `0b5ce47f2`): polarity-aware ambient veil for
light themes (`2e3c76561`), overAmbient legibility for the quality badge
and track info (`949399312`), accents derived from the album palette
(`1a4797085`), reworked cinematic split-card metadata (`460c0cca4`),
karaoke dim of the unsung line part (`68eeb1017`), minimize/maximize in
the window capsule (`aeeb55198`).
## 12. Performance — the GPU campaign (95% → 25%)
Full investigation:
`qbz-nix-docs/qt-frontend/2026-08-11-scenegraph-batches/GPU-COST-INVESTIGATION.md`.
- The shell (dynamic background + Large-bar visualizer) went from **93–97%
GPU / ~34 W to 25–26% / ~13 W** measured on the reference hybrid laptop —
better than the Slint 2.0 UI (35–56%) on the same machine (`f46c5c95d`,
`a2a99a72c`). Root cause: full-window present rate, not render cost — Qt
Quick has no partial repaints, and with KWin compositing on the dGPU every
present costs ~1.2% GPU flat, area-independent.
- **One repaint pulse for the whole shell** (`QbzShell.pulseMs`, ~30 Hz,
`QBZ_PULSE_MS` knob): background atmosphere, visualizer and lyrics engine
tick on the same edge. Standing rule: no continuous animation owns a
`Timer`/`NumberAnimation`/`Behavior`, and a frozen or invisible component
writes nothing.
- Leaks closed: the ambient FBO double-presented; immersive panels (mounted
while the overlay is closed) wrote on every FFT publish; 100 ms Behaviors
animated at display rate (immersive 83% → ~25%).
- Blurred background mode composes its four layers + dim + scrim in **one
opaque ShaderEffect** with verified numerical parity; the image stack
remains as the software-renderer fallback.
- The animated now-playing row indicator returns (eq bars behind the
`play-indicator-animation` pref), mounted on the pulse at zero extra
presents (`eb80e2f36`).
- Elsewhere: tabs build only what is showing, with a build-gate that fails
on hidden-tab construction (`a4c29c796`, `a6b373cb5`); navigation cost is
measurable headless (`801543930`); route changes acknowledged on the next
presented frame (`5b274cab5`); two-phase route commit cut dead-click time
590 → 73–90 ms; the local albums grid actually recycles (`bd9aec144`);
album page track list rides a uniform-cell ListView (`047db8ea9`); a long
restored queue no longer freezes launch (`66ba12bd5`).
## 13. qbzd
- **Event hooks** (PR #700, Filippo Vicentini, merged `e109e88ec`):
`hooks.script` (or `QBZD_HOOK`) names an executable the daemon forks once
per daemon event, the event described in `QBZ_*` environment variables —
the pleezer/shairport-sync push-integration contract, aimed at moOde,
Volumio and DIY boxes (`ee1bbf9b7`), with hardened hook execution
(`251f153a6`).
- **Gapless robustness (#699, Pi 1 GB)**: the successor warms on every track
start and cold gapless hand-offs stream instead of buffering
(`e90ea8cad`), gapless fetches stay off the driver loop (`eb643b9d3`),
plus inhibit backoff (merged `51b7e012f`) — smoke-tested on a real Pi.
- **ALSA hardware volume** controls in the CLI/TUI, same probe and closed
selection as Qt (`c3556fe3e`).
- **QConnect**: qbzd shares the hardened `qconnect-app` core (shared state,
volume mode, task registry adapters), with a hardened ownership lifecycle
(`c927eb9c9`), the quality cap enforced for QConnect (`5bae44286`), and a
cold load of the current track on a state-only resume (`7f9397f91`).
- **State reliability**: bridge state changes deduplicated on the mapped
three-way state (`e336d2d1d`); `TrackStarted` re-emitted when the same
track replays after a stop (`8ec1f3c27`); per-OS instance lock gate
(`f06ffc714`).
- **Packaging & deployment**: standalone qbzd packages with IP masking in
logs (`efa486ba4`); appliance and read-only deployment documented
(`6bfeb88c3`); glibc floor 2.35 on both arches so the Pi streamer keeps
working (`51daf760b`).
- **Misc**: low-memory profile re-wired (#660, `a69491a49`); process-level
rustls `CryptoProvider` installed (#663, `a01097708`); a listen-log
subscriber beside the scrobbler (`3e060d653`); the artist-vector store
logs loudly when it fails to open (`6587201a1`).
## 14. Community-contributed fixes (2.0.x issues)
- Real desktop keyring backends for secrets (#697, `b60ed2c26`) and a
mobile KDF fallback (#695, `4d8779e17`).
- Relocatable CMAF offline bundles (#696, `0a934ae4a`) and a configurable
bundle cache directory (#708, `9aeee886d`); atomic bundle cache
replacement (#707, `388a3591e`).
- Optional mixtape source resolution (#694, `910bee49a`); lofty unified at
0.25.1 (#679, `c039f0d0e`, `b6c69509d`); qbzd rustls TLS provider
(#663, `fe2be63f0`); ALSA buffer/period sizes as `alsa::pcm::Frames`
(`82fea6257`); renderer preference honored with an empty backend env
(#720, `f9b139b44`); preferred-GPU startup hardened (`79f91492b`).
- Hotkeys + Vim keymap (PR #724, Niklas Herder, merged `e993bc1e4`).
- Contributor credits swept into README and About (`b2d3656eb`,
`fbc8e9fa3`, `acc0e3302`); About gained a Sponsors section
(merged `ea02cc238`).
## 15. Build, CI & packaging
- **Release workflows rewritten for Qt** on standard GitHub runners:
reusable Qt Linux build (`929ce39f0`), Linux + aarch64 (`149c021e4`),
macOS with aqt Qt + macdeployqt (`7f98e7b9a`), Snap packs the AppDir and
the Launchpad source recipe is removed (`89030b5b2`), Flatpak repacks the
bare binary on the KDE runtime (`c6bfb90ed`), Windows release workflow
with WiX authoring and LGPL notices (`8fb02401f`).
- **test-crates as a living regression gate** with `scripts/cargo-test.sh`
as its exact local mirror (`ce57551a7`), a Qt gate with the QML audits
shipped in-repo (`077e5fd97`) — six audits including shader bake
(`af8d373d2`) and baked-icon coverage (`ffc2d7d66`) — and a shared
qmllint gate across Linux and Windows (`bf710fb7e`).
- **glibc floors**: x86_64 2.35, arm64 desktop 2.39 (aqt's arm64 qmake
segfaults on 22.04-arm), qbzd 2.35 everywhere, enforced arch-aware in CI
(`51daf760b`).
- Desktop-entry ↔ MPRIS ↔ icon invariant checked against recipes and built
packages (`a1763df25`); macOS Liquid Glass icon via Icon Composer
(`8b77f1ae0`); hardened AUR/Gentoo/updater-manifest publishing
(`ac506e4d6`, `aaf9573c5`, `ecd4fd05c`); MSI smoke verifies both URL
protocols (`353ed7fff`).
- README rewritten for the Qt frontend (`08d924e1b`).
## 16. Dependency bumps
- `cargo update`: 194 semver-compatible bumps, plus rand 0.10,
roxmltree 0.21, base64 0.23, prost 0.14, tokio-tungstenite 0.30,
mpris-server 0.10, mdns-sd 0.21, rfd 0.17 (merged `7d9d3b896`).
- **rusqlite 0.40**: explicit i64 casts (~40 sites); its multi-statement
`execute()` rejection exposed a latent 0.31-era bug — the second statement
of the editions prune never ran; now `execute_batch`.
- **reqwest 0.13**: feature renames, `query`/`form` opt-in; live HTTP paths
re-smoked (login, streaming, scrobbling).
- Deferred to 2.1.1 (coupled to the protected audio stack or multi-day):
symphonia 0.6 + rodio/cpal/alsa wave, cxx-qt 0.10, keyring 4, the
RustCrypto wave, ringbuf 0.5.
---
Full changelog: https://github.com/vicrodh/qbz/compare/v2.0.2...v2.1.0
+112
View File
@@ -0,0 +1,112 @@
# 2.1.0 — Feature inventory (input for the Discussions post)
> Working list, feature-level. Only things that did **not exist before 2.1.0**
> or that got a **major revamp** — independent of the Slint→Qt swap. One or
> two lines each, written to be lifted into the announcement draft.
> Not rendered anywhere; safe to edit freely.
## New platforms
- **Windows support** — QBZ now ships a native Windows build (MSI installer):
same UI, same Rust core, WASAPI exclusive mode with rate probing, media
keys, deep links, single instance.
## New sources & library
- **Jellyfin integration** — browse, search and play your Jellyfin server as
a first-class source, verified bit-perfect against a real server.
- **Subsonic integration** — same treatment for Subsonic-compatible servers
(Navidrome, etc.), also verified bit-perfect.
- **Library Explorer** — a new tree-style browser over your whole collection,
every source in one place, with drag-and-drop into playlists and the queue.
- **Unified local catalog** — the Local Library is now backed by a real
catalog: incremental scans and server syncs, smooth paging with tens of
thousands of tracks, album versions unified across sources.
- **Local tag editor, expanded** (revamp) — bigger editing workspace, safer
writes to file, remote metadata lookup.
## Discs
- **Audio CD playback** — insert a CD and play it, on Linux and macOS, with
metadata lookup and cover art.
- **SACD image playback** — open a SACD ISO and play its stereo program
directly.
- **CD ripping** — a rip wizard that writes tagged, bit-exact, seekable FLAC,
with metadata correction, covers and a provenance log; QBZ remembers the
disc after eject.
## Audio & playback
- **ALSA hardware volume** — opt-in control of your DAC's own hardware mixer,
detected by real capability (never by guessing names); asks when ambiguous,
fails safe when it can't be sure.
- **Playback recovery** — a dead stream or unreachable track can no longer
wedge the queue: QBZ retries with deadlines, then moves on.
- **Stereo scopes** — a new visualizer drawing both channels, oscilloscope
style.
- **Listening history (listen log)** — a private, local log of what you
actually listened to, with its own toggle and clear; the foundation for
offline recommendations.
## Playlists & queue
- **Add to playlist, redesigned** (revamp) — the picker shows which playlists
already contain the track ("Already in"), pins your last-used playlist,
handles multi-track adds, and accepts local and media-server tracks.
- **Album Quick View** (revamp) — peek into any album straight from its card,
wherever you are, without leaving the page.
- **Play later, for real** — album-level "later" now queues after the current
block instead of appending at the end; "Play all later" on artist pages.
- **Extended queue view** — the queue got a full-page view, drag-and-drop
insert at position, reorderable history, and playback history that
survives restarts.
- **Playlist importer expansion** (revamp) — import from playlist files,
JSON, ListenBrainz and Last.fm.
## Qobuz Connect
- **LAN pairing like the official apps** — QBZ announces itself on your local
network so official Qobuz clients can discover and pair with it directly.
- **Connect hardening** (revamp) — shuffle order, volume, buffering states
and handoffs now match official-client behavior exactly, verified against
the iOS app and the Web Player.
## Casting
- **Casting round two** (revamp) — progressive serving, clean shutdown, and
Plex/Jellyfin/Subsonic tracks cast through a local proxy.
- **Visualizers while casting** — a shadow decoder keeps the scopes and
spectrum alive while audio renders on the cast device.
- **Older Chromecasts fixed** — devices with X.509 v1 certificates can cast
again (#730).
## qbzd (headless daemon)
- **Event hooks** — `qbzd settings set hooks.script /path/to/script` (or
`QBZD_HOOK`) runs your executable on every daemon event with `QBZ_*` env
variables — the same push contract as pleezer and shairport-sync, made for
moOde/Volumio/DIY audio boxes (PR #700, Filippo Vicentini).
- **Gapless robustness on constrained hardware** — successors warm on every
track start and cold hand-offs stream instead of buffering, closing the
intermittent-gapless report on a 1 GB Pi (#699); verified on real hardware.
- **ALSA hardware volume in the CLI/TUI** — same capability probe and closed
selection as the desktop app.
- **Standalone packaging & appliance use** — qbzd ships as its own package,
masks IPs in logs, and read-only/appliance deployment is documented.
- **State reliability** — hardened QConnect ownership lifecycle and quality
caps, deduplicated bridge state changes, correct TrackStarted on replays.
## UI & experience
- **Immersive, expanded** (revamp) — more shader scenes, a split view with
lyrics/queue/track info, album-palette accents, karaoke line dimming, and
light themes finally legible over the ambient background.
- **Vim keymap** — the hotkey system grew a Vim mode (PR #724).
- **Sponsors in About** — GitHub and Ko-fi supporters now listed in the app.
## Community fixes worth naming (pre-2.1 issues)
- Real desktop keyring backends for credentials (#697) and a mobile KDF
fallback (#695).
- Relocatable offline cache bundles (#696) with a configurable cache
directory (#708) and atomic cache replacement (#707).
- MPRIS position is exact at read time — external widgets stop drifting.
+107
View File
@@ -0,0 +1,107 @@
# 2.1.0 — __CODENAME__
This one is a little unusual.
After 2.0.x, I honestly thought we had settled down: Slint, no WebView, a fully native UI, and everything seemed to be in the right place.
Naturally, I decided to migrate the UI again.
QBZ is now Qt.
---
## 1. The weird
I know, being able to say "QBZ is written in Slint" was cool. I still like Slint, I think it has a lot of future, and I'll probably use it again somewhere else.
For QBZ, though, resource usage, development speed, and Qt's maturity eventually made the decision pretty easy.
More importantly, this migration fixed something that had been following QBZ since the Tauri days: **QBZ is finally modular**. Playback, library, integrations and UI are now separate pieces instead of growing around each other.
Ironically, Slint deserves some credit for that. Working around its constraints forced better boundaries and architectural decisions that QBZ had never really had before.
So this time changing the face of QBZ didn't mean rewriting the application around it.
Much better.
---
## 2. The good
**Local Library got a lot of love.**
Its backend has been heavily reworked and optimized with one goal in mind: a library with hundreds of thousands of tracks should behave much like one with a few thousand.
I think we're finally there.
And there is plenty to see on the surface too: a classic Library Explorer, broader format support, audio CD playback and ripping, SACD images, a much better metadata editing and matching experience, and tighter integration between your own music and Qobuz.
Plex has been around for a while because that's what I use. **Jellyfin and Navidrome/Subsonic are now first-class sources too**, mostly because people kept asking for them.
Your music can increasingly just be *your music*, regardless of where it happens to live.
---
## 3. The necessary
A lot of 2.1 isn't about adding another button somewhere. It's about making things that already existed behave the way they probably should have from the beginning.
Playlist importing grew considerably and can now bring in playlist files, JSON, ListenBrainz and Last.fm.
The queue finally has a proper full view. If we have a classic Library view, why shouldn't the queue get one too?
**Qobuz Connect and casting got a serious hardening pass.** Pairing, handoffs, buffering, shuffle, volume and casting from local or self-hosted sources are all considerably more solid. Multi-device listening should simply feel faster and less fragile now.
Performance in general is one of the biggest winners of this release. QBZ still looks like QBZ; it just spends much less effort doing it.
macOS also became much less annoying to install. [@afonsojramos](https://github.com/afonsojramos), maintainer of the Mac port, now provides notarized DMGs, and QBZ can also be installed through Homebrew.
No more convincing macOS that yes, you really did mean to open the application.
---
## 4. The bad
The **aarch64 native builds** have unfortunately left some older distributions behind because they don't provide versions of some packages QBZ now depends on.
That includes Debian-based systems older than Bookworm.
I haven't found a clean solution to this yet. If someone has one, I would genuinely love to hear it.
This only affects the native `.deb` and AppImage builds. **Flatpak and Snap remain available** on those systems without this problem.
---
## 5. The unexpected
QBZ now runs on **Windows**.
There is an MSI installer, native playback, bit-perfect output, WASAPI exclusive mode and the things needed to make it actually behave like QBZ.
What it currently lacks is love.
There is a rather explicit disclaimer attached to the Windows build, and I strongly recommend reading it before going straight for the installer:
**Windows is not currently a supported QBZ platform. The build is provided as-is.**
I'll also be transparent about why. I hated using Windows while testing the port — from installing and configuring the box to dealing with its audio setup — and I don't want to spend more time than absolutely necessary inside Redmond's OS for every release.
Sorry. Long-time Gentoo Linux user. Some biases are too old to fix.
Still, the port works and the hard part is already there, so keeping it hidden would be silly.
It is also open for adoption. If someone wants to maintain Windows and eventually give it the same treatment [@afonsojramos](https://github.com/afonsojramos) gave macOS, please get in touch.
---
That's the short version.
I made a GitHub Discussion with screenshots and a more human tour through 2.1.0 if you want to see what actually changed:
[Read the 2.1.0 Discussion →](https://github.com/vicrodh/qbz/discussions)
If implementation details, fixes and issue numbers are more your thing, there is a changelog for that:
[Read the 2.1.0 CHANGELOG →](https://github.com/vicrodh/qbz/blob/main/docs/release-2.1.0/CHANGELOG.md)
Full diff: https://github.com/vicrodh/qbz/compare/v2.0.2...v2.1.0
+4 -2
View File
@@ -36,8 +36,10 @@ package() {
# Install binary
install -Dm755 "qbz" "${pkgdir}/usr/bin/qbz"
# Install desktop file
install -Dm644 "qbz.desktop" "${pkgdir}/usr/share/applications/qbz.desktop"
# Install desktop file. MPRIS publishes DesktopEntry=com.blitzfc.qbz:
# the installed basename MUST match or the GNOME/Plasma media widget
# loses its icon (scripts/packaging/desktop_entry_check.sh).
install -Dm644 "qbz.desktop" "${pkgdir}/usr/share/applications/com.blitzfc.qbz.desktop"
# Install icons (freedesktop hicolor structure)
install -Dm644 "icons/hicolor/32x32/apps/qbz.png" "${pkgdir}/usr/share/icons/hicolor/32x32/apps/qbz.png"
+19 -4
View File
@@ -24,10 +24,22 @@ RESTRICT="mirror strip"
# v2.1+ Qt binary: no webkit/gtk/appindicator. qtsvg provides the imageformats
# plugin used by the shipped SVG icon set.
# Per-arch floors mirror what CI links against (build-qt-linux.yml): amd64
# builds on Qt 6.8.3 / ubuntu-22.04 (glibc 2.35); arm64 builds on Qt 6.9.3 /
# ubuntu-24.04-arm (glibc 2.39) because aqt's arm64 6.8 host tools segfault.
RDEPEND="
>=dev-qt/qtbase-6.8:6[dbus,gui,network,opengl,wayland,X]
>=dev-qt/qtdeclarative-6.8:6
>=dev-qt/qtsvg-6.8:6
amd64? (
>=dev-qt/qtbase-6.8:6[dbus,gui,network,opengl,wayland,X]
>=dev-qt/qtdeclarative-6.8:6
>=dev-qt/qtsvg-6.8:6
>=sys-libs/glibc-2.35
)
arm64? (
>=dev-qt/qtbase-6.9:6[dbus,gui,network,opengl,wayland,X]
>=dev-qt/qtdeclarative-6.9:6
>=dev-qt/qtsvg-6.9:6
>=sys-libs/glibc-2.39
)
media-libs/alsa-lib
media-libs/fontconfig
media-libs/freetype
@@ -61,8 +73,11 @@ src_unpack() {
src_install() {
dobin qbz
# MPRIS publishes DesktopEntry=com.blitzfc.qbz: the installed basename
# MUST match or the GNOME/Plasma media widget loses its icon
# (scripts/packaging/desktop_entry_check.sh documents the invariant).
insinto /usr/share/applications
newins qbz.desktop qbz.desktop
newins qbz.desktop com.blitzfc.qbz.desktop
local size
for size in 32 48 64 128 256; do
+10 -2
View File
@@ -58,17 +58,25 @@ PDEPEND="
S="${WORKDIR}/qbz-${MY_PV}"
src_compile() {
# cxx-qt-build locates Qt through qmake; Gentoo installs it as qmake6
# only, so name it explicitly (CI does the same via the QMAKE env var).
export QMAKE="${QMAKE:-$(command -v qmake6 || command -v /usr/lib64/qt6/bin/qmake)}"
# Symbols are stripped by the workspace release profile. The live Qt graph
# has no Slint memory wall, so let Cargo use the builder's available cores.
cargo build --release --manifest-path crates/Cargo.toml \
# --locked: build exactly the committed Cargo.lock, no resolver drift.
cargo build --release --locked --manifest-path crates/Cargo.toml \
-p qbz-qt || die "cargo build failed"
}
src_install() {
dobin "crates/target/release/qbz"
# MPRIS publishes DesktopEntry=com.blitzfc.qbz: the installed basename
# MUST match or the GNOME/Plasma media widget loses its icon
# (scripts/packaging/desktop_entry_check.sh documents the invariant).
insinto /usr/share/applications
newins packaging/linux/qbz.desktop qbz.desktop
newins packaging/linux/qbz.desktop com.blitzfc.qbz.desktop
local size
for size in 32 48 64 128 256; do