Commit Graph
6658 Commits
Author SHA1 Message Date
vicrodh 1a01abde60 docs(qt): correct empty Windows environment example
cx
2026-08-27 09:05:26 -06:00
vicrodh cc3a11f254 feat(audio): the pure layer of the WASAPI exclusive path
Format ladder, PCM packing and period arithmetic for phase B, with the tests
that make them provable. No COM yet: this half compiles and is tested on
every host, which matters because it is where the platform-specific mistakes
live.

The one that bites: Windows 24-in-32 is LEFT-aligned, the 24 valid bits in
the HIGH bytes, where ALSA's S24_LE is right-aligned. Porting the ALSA
packing verbatim shifts every sample down 8 bits and plays 48 dB QUIET, which
sounds like a volume bug and survives a listening test. There is a test whose
only job is to fail if that ever happens.

Scaling matches alsa_direct exactly (8_388_607 and 32767, not the power of
two), so the two backends cannot disagree about full scale. The plan's test
sketch asserted i16::MIN and -8_388_608; those follow from the OTHER scaling
choice, so the tests assert the behaviour this file actually has.

aligned_period_hns rounds the period so frames * block_align is a whole
number of 128-byte blocks, which is how AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED
stops firing rather than being recovered from. Verified against the two
periods measured on the owner's DAC: 3 ms at 192k 24-in-32 is already
aligned at 576 frames and must not move.

LADDER order comes from research/01; the spike found that this DAC accepts
only S24In32, so the ladder is one rung deep there. It stays a ladder because
other devices are not.

cc
2026-08-27 08:52:13 -06:00
vicrodh 4ccff36139 feat(qt): surface the opened stream's BitPerfectMode on the quality stamp
The mode LED is derived from SETTINGS (output_labels.rs reads
audio.exclusive_mode and the ALSA plugin), so it reports what was ASKED FOR.
Nothing reported what the opened stream actually negotiated, which means
"exclusive worked" and "we asked for exclusive" were indistinguishable - and
that distinction is what phase B's acceptance turns on.

PlaybackEvent already carries bit_perfect_mode, so this is a publish, not new
plumbing: the poll tick maps it to direct | plugin | disabled | unknown and
set_effective_stream dedupes on it alongside the delivered params, so a
steady stream still costs no Qt-thread hop. The quality tooltip appends
"stream: <mode>" on both of its paths; unknown and empty print nothing.

Four technical tokens, so no new translatable string.

cc
2026-08-27 08:49:21 -06:00
vicrodh 559f8bfce6 fix(qt): pin the FreeType font engine on Windows
Qt's default rasteriser on Windows renders text thin and jagged. The owner's
words for the lyrics panel were "como fuente de bloc de notas con extra
alias", and the supplied screenshot shows exactly that: broken strokes and
visible stair-stepping on every line.

It is the RASTERISER, not the font and not the lyrics view. Verified on the
box by launching the same build with
QT_QPA_PLATFORM=windows:fontengine=freetype and comparing the same panel:
identical font, identical sizes, clean glyphs. So the fix belongs at the
application level, where it also cleans up the rest of the UI.

Applied only when the variable carries no real value. `offscreen` is what
the release smoke gate passes here and overwriting it would make the gate
test a platform nobody ships. Empty counts as absent for the same reason the
renderer preference now does: a cleared variable is present-with-no-value,
which var_os().is_some() reports as set.

cc
2026-08-27 08:49:21 -06:00
vicrodh 4f31e8312d fix(qt): an EMPTY Qt backend env var must not disable the renderer preference
apply_renderer_preference gated on var_os("QSG_RHI_BACKEND").is_some(), which
answers YES for a variable that is present with no value. Clearing a variable
rather than unsetting it is the common way to end up there - `QSG_RHI_BACKEND=
./qbz` in a shell, and on Windows PowerShell's SetEnvironmentVariable(name,
$null), which leaves the name present and empty. The whole preference then
stopped applying, the user's persisted Settings choice included, and the only
clue was one info line claiming an explicit backend was present.

Found on the Windows box, where a test harness cleared the variable that way
and the renderer arm silently never ran. It reproduces on Linux the same way;
this is not a platform fix.

QBZ_RENDERER, read a few lines below, has always filtered the empty string.
This applies the same rule to the two Qt variables. The predicate is split
from the lookup so the three new tests exercise it without mutating
process-global env, which would race the other tests in the binary.

cc
2026-08-27 08:39:53 -06:00
vicrodh 93b506dcd6 fix(paths): route the hand-rolled file:// strips through local_path
A regression from 8baf68db8, reported from the running app: the album and
artist header gradients stopped appearing on Windows.

Six sites undid a file:// URL by hand with strip_prefix/trim_start_matches.
Before the URL builder was fixed they worked BY ACCIDENT: file_url produced
the broken "file://C:\Users\x", and stripping seven characters off that left
a valid Windows path. With the URL correct at "file:///C:/Users/x" the same
strip yields "/C:/Users/x", which opens nothing - so the fix to the QML-
facing half broke the Rust-facing half everywhere the inverse was open-coded.

atmosphere_qt.rs is the one that showed: it decodes the cover to build the
128x128 field the header gradient IS, so a failed open means no gradient at
all, with nothing logged. album_qt.rs feeds the same pipeline plus the flat
tint, ambient_qt.rs extracts the colour triad, artwork_qt.rs uses the string
as BOTH a cache key and a path, myqbz_detail_qt.rs hands a bare path to the
artwork channel, and qbz-source returns an ArtRef::File.

local_path also undoes the three percent-escapes these sites never did, so a
cover under a path holding # ? or % now opens on every platform - previously
it failed on all of them. The two sites that hash the string will miss their
cache once and regenerate.

Three strips deliberately stay: notify.rs is cfg(linux, macos); the
playlist-import one takes a filename STEM, where a leading slash cannot
matter; and qbz-theme parses a COSMIC desktop config Windows never reads.

The plan's step-8 grep could not have caught these - it searches for
starts_with('/') and format!("file://, not for the inverse.

cc
2026-08-27 04:27:13 -06:00
vicrodh 249adc77b3 fix(audio): identify Windows output devices by endpoint id, not friendly name
Windows endpoint friendly names are not unique. Measured on this box, FOUR
active render endpoints resolved to three display strings, and two were both
called "Altavoces": the onboard Realtek and a Cambridge Audio USB DAC.
device_filter dedups on the display, so the DAC was folded away and could
not be selected at all - and a DAC you cannot select cannot be sent a
bit-perfect stream, so this also blocked phase B.

device_filter.rs:20-23 states the opposite in a comment - "macOS/Windows
device names carry unique displays, so they pass through untouched". That is
the third comment in this tree asserting a mechanism that does not hold.

Nothing new is queried: cpal 0.17.3 already fills the fields this backend
ignored. name() is the ENDPOINT and repeats; driver() is the ADAPTER and is
what tells them apart; id() is the stable WASAPI endpoint GUID. On Windows
the id becomes the GUID, the display becomes "endpoint (adapter)", and the
adapter lands in the description field that was hard-coded None. The default
device is matched by id there too, since three endpoints shared a name.

Additive cfg arms only, per the protected-core rule: every other platform
takes the same (name, name, None, name == default_name) tuple it took
before, byte for byte. The open path keeps the name comparison as a fallback
so a device_id saved before this change still resolves.

Verified against the real hardware, before and after, with a standalone cpal
probe: the DAC goes from DROPPED to listed, labelled and flagged default.

cc
2026-08-27 04:07:31 -06:00
vicrodh fc0d35e295 fix(qt): platform capability flags, and hide the audio rows nothing can honour
Adds isWindows, traySupported, systemNotificationsSupported and
rendererSelectable to the settings document, so Settings QML keeps reading
ONE document the way it already does for isMacos. The two any(...) lists
flip to include Windows when plan A-2 T3/T5 land; they point at that line.

Then the Audio tab, measured on the box with the tab open:

HiFi Wizard was live on Windows. qbz-dac-wizard-core generates a
PipeWire/WirePlumber config and knows no other stack, so the wizard cannot
finish anywhere but Linux. Hidden, not disabled - a disabled row invites a
bug report. This is not the reference's backend-is-pipewire gate returning:
inside Linux every backend still sees it, per the 2026-08-11 ruling in that
file's own comment.

Reserve DAC is D-Bus org.freedesktop.ReserveDevice1, Linux only. On Windows
exclusive mode IS the reservation; on macOS it is Hog Mode.

Exclusive mode was visible with no backend able to honour it. It now follows
the backend: Linux unchanged (always visible, enabled only on ALSA), hidden
on macOS, and on Windows it appears when Phase B publishes backendIsWasapi.
QML reads an absent doc property as undefined and undefined === true is
false, so this is correct today and correct again the day the field lands.
The DSD row follows the same pair.

Main.qml no longer boots the wizard off Linux, where its row is hidden and
the audio-stack probe would reach nothing.

Linux sees no change: every gate is isLinux-true or an added || on a field
that is undefined there. Five QML audits pass.

cc
2026-08-27 04:01:13 -06:00
vicrodh 7135c841cb build(windows): embed the icon, VERSIONINFO and a longPathAware manifest
winresource, not winres: winres has been unmaintained since 2021 and this
is its maintained fork. Gated to cfg(windows) as a target build-dependency,
so no other platform resolves it at all.

The manifest declares longPathAware and NOTHING else. Qt 6 sets
PerMonitorV2 itself at startup and a manifest DPI declaration is immutable,
so the two together either log a permanent COM error 0x5 or degrade
scaling - Qt owns DPI awareness.

The .ico carries 256/128/64/48/32. The four small entries are BMP and only
256 is PNG: measured on this box, System.Drawing.Icon (GDI+) throws
ArgumentOutOfRangeException in ToBitmap() on a PNG-payload entry, while WIC
decodes it. 256 has to be PNG because the directory encodes its size as 0.
Pixels verified against the source PNG at four points including an
asymmetric one, which is what proves the BGRA order and the bottom-up flip.

cc
2026-08-27 03:46:29 -06:00
vicrodh 77f7840ae7 fix: one hostname resolver, honouring COMPUTERNAME
Three copies read HOSTNAME and then /etc/hostname. Windows has neither, so
every Windows install fell through to the same last-resort word and they
collided on Qobuz Connect. One implementation in qbz-app; HOSTNAME keeps
precedence so Linux behaviour does not move.

qbzd/src/qconnect/transport.rs was a fourth copy the plan did not list, and
the settings_qt.rs one it did list no longer exists on this branch. Both
callers keep their own last-resort word - "Desktop" here, and qbzd's own -
because they are user-visible and mean different things.

qbz-credentials/src/lib.rs:273 also reads HOSTNAME and is deliberately NOT
touched: it is a salt in the KDF fallback chain, not a device label, so
adding COMPUTERNAME there would change the derived key on Windows and make
every already-encrypted credential file undecryptable. That is W17.

cc
2026-08-27 03:46:05 -06:00
vicrodh 45e0be125e feat(qt): expose is_windows on the shell bridge
Windows was neither is_macos nor is_linux, so QML could not name it and
every two-way platform ternary handed it the Linux arm by default.
Compile-time constant like the other two.

This is infrastructure, not a fix: the audit below found NO site that
currently resolves wrong on Windows. It is the property the settings
per-platform work and the A-2 chrome slice branch on.

Every two-way platform branch, and what Windows gets today:

  Main.qml:145 chrome flags      -> Qt.Window. default_use_system_title_bar
                                    is !cfg!(macos), so systemTitleBar is
                                    true on Windows and the ternary never
                                    reaches the macOS/Linux arms. With the
                                    title bar off it takes FramelessWindowHint,
                                    which is also the right arm on Windows -
                                    ExpandedClientAreaHint paints nothing there
  Main.qml:241 macOS diagnostic  -> skipped
  Main.qml:435 macChromeTimer    -> not running
  Main.qml:640 traffic-light inset -> skipped
  ImmersiveHeader.qml:42         -> no macOS inset
  HeaderBar.qml:89 drawn cluster -> gated on showWindowControls, which the
                                    native title bar leaves off; only the
                                    native buttons are drawn (confirmed on screen)
  HeaderBar.qml:104              -> no macOS inset
  AppearanceSettings.qml:493     -> hidden (useSystemTitleBar is true)
  AppearanceSettings.qml:510,521 -> visible; both are meaningful on Windows
                                    and 521 is the route to custom chrome
  AppearanceSettings.qml:583,603 -> hidden
  AppearanceSettings.qml:630-646 -> renderer group hidden
  hotkeys_qt.rs:423,961          -> Ctrl glyphs
  main.rs:3099-3102              -> has its own Windows arm
  renderer_qt.rs:420             -> gpuSelectable false, GPU row hidden
  settings_qt.rs:469             -> system title bar on by default
  settings_qt.rs:2258            -> is_macos false

cc
2026-08-27 03:46:05 -06:00
vicrodh 0ba1b05be9 fix(paths): undo verbatim prefixes, keep the UNC authority, leave relative alone
Adversarial review of the previous commit found four defects, three of them
mine and one of them fatal to the Local Library on Windows.

fs::canonicalize RETURNS extended-length syntax on Windows and
qbz-library/src/scan.rs stores that string for every scanned file, so the
helper saw \?\C:\Music\cover.jpg, read it as a UNC path whose server was
"?", and emitted file://%3F/C:/Music/... - every local cover, gone. Both
verbatim spellings are now undone, \?\UNC\ included.

local_path dropped the // that a UNC authority stands for, so the round trip
returned nas/music/x, a path that names nothing. It now returns
//nas/music/x, which is also what QUrl::toLocalFile gives back.

file_url pushed a third slash for any path that did not start with one, so a
relative artwork_path changed from file://covers/x to file:///covers/x on
Linux. Neither resolves - a relative path is not a valid file:// URL - but
Linux behaviour does not move in a Windows port. The extra slash is now
emitted only for a drive path.

is_local_abs_path now requires a separator after the colon, so C:cover.jpg
is drive-RELATIVE and no longer claimed as absolute on either platform.

The two residual ambiguities - a Unix path whose first component is a single
letter plus a colon - are documented at the top of the module rather than
papered over: no string algorithm separates them, and deciding by host would
put the failure back on the machine that ships.

Four regression tests added, one per defect.

cc
2026-08-27 03:37:38 -06:00
vicrodh 8baf68db84 fix(paths): one file:// helper and a shape-based absolute-path test
Seven sites built format!("file://{p}") by hand or tested starts_with('/')
for "absolute path". Both are Unix-only: file://C:/... parses the drive
letter as the URL HOST, and C:\... does not start with '/'. On Windows
that silently cost every album cover, every runtime-tinted icon and the
local folder tree - a URL that resolves to nothing renders as an empty
rectangle, not as an error, so nothing complained.

qbz_models::fs_url now owns file_url / local_path / is_local_abs_path,
deciding by path SHAPE (POSIX, drive, UNC) rather than by host, so the
tests run and fail everywhere rather than only on Windows.

Linux behaviour is unchanged, deliberately: the backslash-to-slash rewrite
fires only for a Windows-shaped input, because a backslash is a legal
filename character on Unix and rewriting it would point /home/v/AC\DC at
a directory that does not exist. There is a test for exactly that.

Three of the seven sites were not in the plan's list and were found by its
step 8 grep: local_ephemeral.rs, local_playlist_qt.rs, playback_qt.rs.
Two starts_with('/') hits stay and are not local-path tests:
qbz-library/src/mount_info.rs is cfg(linux) mount-point arithmetic, and
qbz-theme's is parsing a COSMIC desktop config that Windows never reads.

cc
2026-08-27 03:22:32 -06:00
vicrodh 7129b9f70e chore: gitattributes for shaders and LF text on Windows checkouts
The committed .qsb are binary but carry raw CR bytes, so a future blanket
`* text` would mangle them. Everything else is pinned to LF so a checkout
with autocrlf on cannot turn 2.1.0 into 2.1.0<CR> in a version grep, or
CRLF the GLSL and QML sources.

.rs is pinned too, which the plan's list omitted: it is the largest text
set in the tree and the same class of invisible breakage applies.
git add --renormalize . stages nothing, so no file changes today.

cc
2026-08-27 02:45:03 -06:00
vicrodh 3e8a1cf942 build(windows): GUI subsystem so no console opens beside the app
windows_subsystem was declared nowhere, so the app carried a console
window on Windows. The file logger is unaffected; only the inherited
stdio console goes.

The offscreen smoke gate has not run against this yet. If smoke.log comes
back empty the fix is AttachConsole(ATTACH_PARENT_PROCESS) behind
cfg(windows), not reverting this; there is a note at the attribute saying
so.

cc
2026-08-26 23:07:25 -06:00
vicrodh 8a08dcb69e fix(qt): Windows arm for the gl renderer pref; pin the Basic controls style
The gl/gles/femtovg pref forced QSG_RHI_BACKEND=opengl on every non-macOS
host, dragging Windows off its healthy d3d11 default onto desktop GL. That
is a downgrade, not the GPU tier the pref means, so Windows takes the same
remap macOS already had.

And with no explicit style Qt loads Windows on Windows and Fusion on
Linux, never Basic, so the popups without an explicit background inherit
native chrome. Pinned before QGuiApplication, since it must run before the
first QtQuick.Controls import, and windeployqt then ships one style.

cc
2026-08-26 23:07:25 -06:00
vicrodh 42bc4ba1ec fix(windows): do not panic in souvlaki without an HWND; open folders via open::that
souvlaki's Windows backend does config.hwnd.expect() at
platform/windows/mod.rs:57, so the None we passed was a panic inside a
tokio task on the first session, not the Err the match was written for.
The comment on that field claimed init would merely fail; it was read from
the comment, not the mechanism. No HWND is wired yet, so spawn() bows out
on Windows and the body moves to spawn_native, which is compiled out
there. Phase A ships without media keys and says so.

The offline-folder opener spawned xdg-open on every non-macOS host,
Windows included, where it does not exist. Route it through the open crate
like the other eleven sites in this crate; Linux and macOS reach the same
two binaries as before.

cc
2026-08-26 23:07:11 -06:00
vicrodh f06ffc7141 fix(qbzd): gate the instance lock per OS
lock.rs imported std::os::unix and called libc::flock unconditionally, so
a workspace test run could not compile on Windows.

The Windows arm locks ONE byte at offset 1 GiB rather than the whole file.
Byte-range locks are mandatory there, not advisory like flock: measured on
this box, a whole-file lock from offset 0 makes the losing instance's
read_to_string fail with ERROR_LOCK_VIOLATION, so AlreadyRunning would
always carry None - the pid is the only reason that variant exists. With
the lock byte past any content the pid still reads back, the same handle
conflict is still detected, and the lock still releases when the File
drops. Reserving a region past EOF is what SQLite's Windows VFS does.

Only ERROR_LOCK_VIOLATION means contention; a filesystem that does not
implement byte-range locks answers ERROR_INVALID_FUNCTION and must surface
as Io, not as a daemon that is not running. The unix arm keeps its
existing any-failure-means-busy behaviour unchanged.

cc
2026-08-26 23:07:02 -06:00
vicrodh 57f900bec4 fix(local-catalog): Windows arms for free-space preflight and bootstrap lock
The not(unix) arm returned Err and both preflight() and prepare()
propagate it, so the catalog never bootstrapped on Windows and the Local
Library died at preflight.

GetDiskFreeSpaceExW on the nearest existing ancestor, since the catalog
root may not exist on first run; LockFileEx with FAIL_IMMEDIATELY, mapping
ERROR_LOCK_VIOLATION to BootstrapBusy like the flock arm and any other
error to Io. Nothing reads this lock file's contents, so the whole-file
range is safe here.

cc
2026-08-26 23:06:52 -06:00
vicrodh b94e0e6cdb fix(secrets): select the keyring backend per target and refuse the mock
qbz-credentials still asked for the Linux features on every target with
default_features on. keyring 3 has no default backend: without the
per-platform feature it exports the in-memory mock, whose writes succeed
and whose contents die with the process, so the offline-cache master key
was regenerated every launch and every wrapped blob failed AES-GCM on the
next start, with the logs reporting a working keyring. It survived only
because Cargo unified features with qbz-secrets, which is the fragility
the per-target block exists to remove.

Also refuse a non-disk-backed backend at runtime and take the KDF path.
Measured in keyring-3.6.3, not assumed: windows.rs, macos.rs and
secret_service.rs all inherit the trait default UntilDelete
(credential.rs:171); mock.rs:228 overrides it to EntryOnly. Testing for
ProcessOnly, as first drafted, would never have fired.

cc
2026-08-26 23:06:41 -06:00
vicrodh 18a3d01152 build(qt): pass the MSVC conformance flags Qt 6.9 requires
Neither cc-rs nor cxx-qt-build passes them, and without both nothing that
includes a Qt header compiles on Windows.

/Zc:__cplusplus: MSVC reports __cplusplus as 199711L whatever /std: says,
and qcompilerdetection.h:1317 rejects that value outright on every
translation unit, ours and moc's alike.

/permissive-: measured on Qt 6.9.3, the headers do not parse in MSVC's
default mode - qtmochelpers.h:262 C2065 'result', qcomparehelpers.h:1348
C2968 recursive alias, and C2737 on every moc staticMetaObject.

Applied to both cc builders: this crate's own, which compiles the nine RHI
items, and cxx-qt's. Patching only the latter changed nothing, because the
failure came from the former. Gated on the target env, so the Linux and
macOS builds cannot see either flag.

cc
2026-08-26 23:06:31 -06:00
vicrodh 6beb1e695e build(qt): slash-normalise qrc aliases and find qsb.exe
qt-build-utils writes the collected path verbatim as the qrc alias, and
read_dir joins with a backslash on Windows, so none of the 1469 aliases
resolved: 1106 icons, 9 fonts and 21 shaders would each fail with
"Cannot open". Normalise only when the build host is Windows, since a
backslash is a legal filename character on Unix.

Also probe qsb.exe and QT_ROOT_DIR/bin, because Path::is_file never
appends .exe, and fix the SpectrumBand hand-bake comment, whose command
lacked --hlsl 50 and the GLES variants that build.rs actually passes.

cc
2026-08-26 23:06:20 -06:00
vicrodh d9ed19eb64 refactor(mixtape): move provider resolution to source registry
cx
2026-08-26 21:38:58 -06:00
vicrodh 910bee49a5 refactor(mixtape): integrate optional source resolution (#694)
cx
2026-08-26 21:31:52 -06:00
vicrodh b6c69509df build(deps): unify lofty at 0.25.1
cx
2026-08-26 21:31:47 -06:00
vicrodh c039f0d0ee build(deps): integrate lofty 0.24 update (#679)
cx
2026-08-26 21:29:02 -06:00
vicrodh b2d3656eb4 docs(contributors): credit adopted external fixes
cx
2026-08-26 21:28:56 -06:00
vicrodh a4ddf64d7f docs(macos): recommend notarized downloads
cx
2026-08-26 21:15:14 -06:00
vicrodh b60ed2c268 fix(secrets): integrate real desktop keyring backends (#697)
Preserve Afonso’s restart regression test while selecting native stores only on Linux, macOS and Windows. Unsupported targets take the persisted KDF path instead of keyring’s successful in-memory mock; the stale workflow change is deliberately excluded.

cx
2026-08-26 20:59:27 -06:00
vicrodh 3b0fea51e1 docs(macos): integrate community signed install option (#705)
Keep official upstream releases and Gatekeeper guidance as the primary route. Present Afonso’s notarized Homebrew cask and DMGs as an independent community alternative with explicit provenance and endorsement boundaries.

cx
2026-08-26 20:53:19 -06:00
vicrodh 388a3591e2 fix(qobuz): integrate atomic bundle cache replacement (#707)
Preserve Afonso’s same-directory temp-file replacement on top of the configurable cache root from #708. Cover the complete explicit-root round trip and document the exact process-crash and concurrent-writer guarantees.

cx
2026-08-26 20:50:33 -06:00
vicrodh 811f6e978c ci: test-crates uses all cores (jobs=2 was a Slint-era memory tier)
Measured cold on the 4-vCPU CI VM: CARGO_BUILD_JOBS=4 finishes the
workspace test in 392 s against 589 s at jobs=2, with a 2.7 GB peak on
a 10 GB guest. The throttle protected a 30 GB rustc that no longer
exists in this graph. Leave the job count to cargo in the workflow and
in scripts/cargo-test.sh alike; --no-fail-fast and the 20-minute limit
stay.

cc
2026-08-26 20:22:17 -06:00
vicrodh f53543d1a4 ci: isolate the offscreen boot (private bus, private XDG dirs)
The single-instance lock is a D-Bus well-known name, so on a developer
box with QBZ open the debug binary presented the running instance and
exited with one log line, failing the gate for the wrong reason. The
smoke now runs under dbus-run-session with its own XDG dirs — never the
developer's config or session — in the script and the workflow alike.

cc
2026-08-26 20:14:23 -06:00
vicrodh ce57551a71 ci: test-crates as a living gate with cargo-test.sh as its exact mirror
Per the 2026-08-26 rule: the workflow's test job runs scripts/cargo-test.sh
(CARGO_BUILD_JOBS=2, --no-fail-fast, 20-minute limit), the script
documents what --workspace covers now that the Slint crates are gone,
and QT=1 runs the Qt gates locally in the same order as the shader-gate
and qt-gate jobs: audits, shader bake, Slint-free graph, cargo test
-p qbz-qt, offscreen boot.

cc
2026-08-26 19:11:58 -06:00
vicrodh 08d924e1b8 docs: rewrite the README for the Qt frontend
The previous README described the Slint build: the memory wall, the
slint-*.sh dev loops, nightly + mold, a Debian dependency list without
Qt, seven locales, the wgpu/femtovg renderer table. All of it is
replaced with the Qt reality: cxx-qt, Qt 6.8 floor with private
headers, scripts/qt-run.sh, the measured build times, the real
QBZ_RENDERER semantics. Adds what shipped since: Jellyfin, Subsonic,
CD/SACD playback and ripping, the metadata editor, the playlist
manager. Adds a table of contents, a "Reporting a problem" section
(Settings > Share logs), the LLM/AI tools statement, the multichannel
DSD known issue, and corrects the Flatpak bit-perfect note (it works;
one device-reservation grant, shown in Settings > Flatpak).

cc
2026-08-26 19:11:01 -06:00
vicrodh af8d373d2c ci: shader bake gate; audits find Qt's qmltypes; builds never re-bake
qml_resolution_audit hard-coded /usr/lib64/qt6/qml, so on a runner it
saw zero Qt types and reported every Rectangle as unresolved; it now
takes QBZ_QT_QML_DIR (authoritative), then QT_ROOT_DIR, then the distro
layouts, and fails loudly when none has plugins.qmltypes. test-crates
runs the audits after the Qt install, pointed at it.

build.rs re-bakes a shader only when its .frag is newer than its .qsb,
which makes a fresh checkout nondeterministic whenever qsb is on PATH.
Release and test builds now install Qt without qtshadertools, so the
committed .qsb ship as-is, and a new shader-gate job bakes every shader
to a temp dir with the pinned qsb using build.rs's own target lists
(read from the file, so they cannot drift). It is red today at 6.8.3:
plasma.frag's uint literals do not bake for the GLSL 120 target with
qsb 6.8 (6.9+ accept them) — that is the finding, not a false positive.

cc
2026-08-26 19:11:01 -06:00
vicrodh c51e004fc2 ci: keep the -p qbz gate out of test-crates
test-crates is meant to merge first, ahead of the release-workflow
rewrites; a gate over .github/workflows/ would fail on the still-Slint
release files until those land. The gate stays in release-linux and
release-linux-aarch64, which are the files it protects.

cc
2026-08-26 19:11:01 -06:00
vicrodh dd6527043b ci: only test-crates runs from pre-release; drop the Slint excludes
The Slint crates are gone from the workspace, so cargo-test.sh's four
--exclude flags were stale; qbz-qt is the only exclusion now (it is the
one crate that needs Qt, and the sole consumer of wayland-sys — the
reason the workspace test fails on a runner without libwayland-dev).
Release workflows write their caches from main only: nothing but
test-crates.yml is meant to run from pre-release.

cc
2026-08-26 19:11:01 -06:00
vicrodh 7bd5cb1ec6 scripts: aarch64 build scripts for the Qt binary
build-aarch64-linux.sh builds -p qbz-qt natively with the distro's Qt
(gated at >= 6.8, the tree's floor; Debian 13 / Ubuntu 25.04 / Fedora
41 qualify), installs rustup when absent, forces one rustc at a time on
<= 4 GB boxes, and runs the same offscreen smoke as CI. Its CROSS mode
is retired honestly: cxx-qt runs the target Qt's qmake during the
build, which cross-rs's Ubuntu 20.04 images cannot do. The qbzd script
and Cross.toml keep cross-compiling the daemon, with the Qt-free dep
set.

cc
2026-08-26 19:11:01 -06:00
vicrodh 04fd94d41a packaging: Qt dependencies for the flake, AUR and Gentoo
flake builds -p qbz-qt against nixpkgs qt6 with wrapQtAppsHook; the
AUR PKGBUILD and the Gentoo binary ebuild add qt6-base/qtdeclarative
>= 6.8 (both distros ship 6.9), since their tarball carries the bare
binary.

cc
2026-08-26 19:11:01 -06:00
vicrodh 077e5fd97e ci: qt gate in test-crates; ship the QML audits in the repo
The five static QML audits move from the docs checkout to
scripts/qml-audits so CI runs the same gate as scripts/qt-run.sh
(which now defaults to the in-repo copy). New qt-gate job: audits,
Slint-free graph check, cargo test -p qbz-qt, and an offscreen boot of
the debug binary that fails on any QML complaint. cargo-test.sh excludes
qbz-qt (it needs Qt) so the no-UI job stays cheap.

cc
2026-08-26 19:11:01 -06:00
vicrodh c6bfb90ede ci: flatpak bundle repacks the bare binary on the KDE runtime
org.kde.Platform 6.9 provides Qt above the 6.8 floor; the manifest no
longer runs cargo, needs no rust/llvm SDK extensions and no swap. The
workflow takes the binary from build-qt-linux.yml, builds qbzd
alongside, and boots the installed bundle offscreen inside the sandbox
before publishing. Same shape as the Flathub manifest.

cc
2026-08-26 19:11:01 -06:00
vicrodh 89030b5b2a ci: snap packs the AppDir; remove the Launchpad source recipe
core24 ships Qt 6.4.2 and the tree needs 6.8, so neither stage-packages
nor a remote source build can provide Qt: the dump recipe now takes the
AppDir tree from build-qt-linux.yml (both arches) and the root
snapcraft.yaml plus its packaging/snap symlink are deleted. snap-slint
becomes snap-qt with the non-Qt runtime only.

cc
2026-08-26 19:11:01 -06:00
vicrodh 7f98e7b9a7 ci: rewrite release-macos with aqt Qt and macdeployqt
The Slint workflow copied the bare Mach-O into the bundle; a Qt binary
is not self-contained. Now: universal aqt Qt (Homebrew's split layout
breaks macdeployqt with rpath errors), MACOSX_DEPLOYMENT_TARGET 12.0,
the arm_acle.h include for Qt 6.9's qyieldcpu.h on Apple clang 21,
macdeployqt -qmldir, then an offscreen boot of the bundle with the Qt
install hidden before signing and packing. Intel builds move to
macos-15-intel. Measured on the Mac mini M2 2026-08-25.

cc
2026-08-26 19:11:01 -06:00
vicrodh 149c021e47 ci: rewrite release-linux and aarch64 around the Qt build
Both call build-qt-linux.yml. deb/rpm/AppImage are packed from the
AppDir (Qt bundled under /opt/qbz, /usr/bin/qbz symlink, RPATH and
qt.conf resolve through /proc/self/exe — no wrapper, which would leak
LD_LIBRARY_PATH into spawned processes); the AUR/Gentoo/Flathub tarball
keeps the bare binary. Every published file name is unchanged. Adds the
gate that no workflow builds `-p qbz` (the frozen Slint bin shares the
name and target dir). The two files differ only in the ARCH block.

cc
2026-08-26 19:11:01 -06:00
vicrodh 929ce39f09 ci: add the reusable Qt Linux build, drop build-slint
One workflow_call recipe for every Linux release path: aqt Qt (the
runner's own Qt is 6.4 and lacks QtQuick.Effects), Rust stable, no
memory tiers, ubuntu-22.04 for the glibc 2.35 floor. Produces the bare
binary and a self-contained AppDir (linuxdeploy + Qt plugin, Wayland and
offscreen platform plugins added explicitly), then boots the AppDir
offscreen with the Qt install removed from the environment and fails on
any QML complaint. Dispatchable on its own as the pre-flight.

Recipe measured on the 24.04 CI VM 2026-08-25 (contracts in
qbz-nix-docs/cicd/2026-08-25-qt-workflows-contracts).

cc
2026-08-26 19:11:01 -06:00
vicrodh b292e84abb chore(deps): integrate serde_json 1.0.151 (#650)
cx
2026-08-26 17:45:51 -06:00
vicrodh 0a934ae4a0 fix(offline-cache): integrate relocatable CMAF bundles (#696)
cx
2026-08-26 17:44:20 -06:00
vicrodh 4d8779e17f fix(secrets): integrate mobile KDF fallback (#695)
cx
2026-08-26 17:43:55 -06:00
vicrodh 9aeee886db feat(qobuz): integrate configurable bundle cache directory (#708)
cx
2026-08-26 17:42:23 -06:00