docs(release): prepare 2.1.0 notes and packaging
cx
This commit is contained in:
6 files changed
+619
-38
No files matched your search
+367
-30
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user