Build the catalog side by side from read-only local, Plex, Jellyfin, and Subsonic cache snapshots after the first useful Qt frame. Persist bounded batch checkpoints, preflight disk capacity, validate materializations, and activate generations atomically while preserving the existing reader as fallback. cx
272 lines
14 KiB
TOML
272 lines
14 KiB
TOML
[package]
|
|
name = "qbz-qt"
|
|
description = "Qt/QML frontend POC for QBZ (cxx-qt bridge over the UI-agnostic backend)"
|
|
version.workspace = true
|
|
edition.workspace = true
|
|
license.workspace = true
|
|
|
|
[[bin]]
|
|
# The SHIPPED name is `qbz` (owner, 2026-08-04). The package stays
|
|
# `qbz-qt`, so `cargo build -p qbz-qt` is unchanged; only the artifact
|
|
# moves to target/<profile>/qbz.
|
|
#
|
|
# ⚠ `crates/qbz` (the Slint binary) declares the SAME bin name and
|
|
# shares this workspace's target dir, so both write
|
|
# target/<profile>/qbz and the last build wins. That is survivable only
|
|
# because the Slint binary is never built in this worktree (the qbz-ui
|
|
# 20-30 GB wall). If it ever is, whoever built last owns the file — and
|
|
# a smoke against "the wrong qbz" is silent.
|
|
name = "qbz"
|
|
path = "src/main.rs"
|
|
|
|
[dependencies]
|
|
cxx = "1"
|
|
cxx-qt = "0.7"
|
|
cxx-qt-lib = { version = "0.7", features = ["qt_full"] }
|
|
|
|
tokio = { workspace = true }
|
|
log = { workspace = true }
|
|
# Hard-exit watchdog (main.rs arm_hard_exit_watchdog): `_exit` terminates
|
|
# without running the atexit chain — a hang inside Qt's atexit destructors is
|
|
# one of the wedges the watchdog exists to cut short. Already in the lock as
|
|
# a transitive dep of half the tree; this only makes it direct.
|
|
libc = "0.2"
|
|
qbz-disc = { path = "../qbz-disc" }
|
|
qbz-rip = { path = "../qbz-rip" }
|
|
rand = { workspace = true }
|
|
serde = { workspace = true, features = ["derive"] }
|
|
serde_json = { workspace = true }
|
|
reqwest = { workspace = true }
|
|
urlencoding = "2"
|
|
open = "5"
|
|
dirs = "5"
|
|
|
|
# Share links (src/share_qt.rs, the port of crates/qbz/src/share.rs:32-70).
|
|
# Rust owns the clipboard on this side because the toast does: cxx-qt-lib 0.7
|
|
# ships no QClipboard binding, and QML cannot publish QbzShell.toastJson, so
|
|
# splitting the copy and its confirmation across the boundary would buy
|
|
# nothing. Same pin as the reference (crates/qbz/Cargo.toml:68), comment
|
|
# included:
|
|
#
|
|
# wayland-data-control: Wayland-native clipboard via the data-control
|
|
# protocols (GNOME/KDE/sway/niri/Hyprland), falling back to X11 when Wayland
|
|
# is absent (#514 — without it arboard is X11-only and every copy fails on
|
|
# Wayland-only sessions).
|
|
arboard = { version = "3", features = ["wayland-data-control"] }
|
|
|
|
qbz-core = { path = "../qbz-core" }
|
|
qbz-app = { path = "../qbz-app" }
|
|
qbz-audio = { path = "../qbz-audio" }
|
|
# The HiFi Wizard's frontend-agnostic core. NOT `qbz-dac-wizard` — that one is
|
|
# the Slint adapter and pulls in qbz-ui + slint, the ~1.6 M-line generated
|
|
# module this whole track exists to stay out of.
|
|
qbz-dac-wizard-core = { path = "../qbz-dac-wizard-core" }
|
|
# Vulkan enumeration for the Preferred-GPU row — real adapter NAMES, MODELS and
|
|
# types, in the exact order `QT_VK_PHYSICAL_DEVICE_INDEX` counts them (both are
|
|
# `vkEnumeratePhysicalDevices`). This is the Qt analogue of what the reference
|
|
# does with `wgpu::Instance::enumerate_adapters`.
|
|
#
|
|
# `ash` is a thin FFI binding that dlopens libvulkan at RUNTIME: no Vulkan SDK
|
|
# at build time, and a box without libvulkan simply enumerates nothing. It was
|
|
# already in the workspace lock (wgpu pulls it), so it costs no new compile.
|
|
ash = "0.38"
|
|
qbz-qobuz = { path = "../qbz-qobuz" }
|
|
qbz-models = { path = "../qbz-models" }
|
|
qbz-cache = { path = "../qbz-cache" }
|
|
qbz-offline-cache = { path = "../qbz-offline-cache" }
|
|
qbz-library = { path = "../qbz-library" }
|
|
# Derived, rebuildable Local Library read catalog. The Qt app only schedules
|
|
# its background bootstrap here; no Qt type crosses into the catalog crate.
|
|
qbz-local-catalog = { path = "../qbz-local-catalog" }
|
|
# MyQBZ: Mixtapes & Collections backend (schema + migrations, repo, shuffle,
|
|
# enqueue). Headless and frontend-agnostic (ADR-006) — the same crate the
|
|
# Slint app uses (crates/qbz/Cargo.toml:96), so the two frontends share
|
|
# library.db's mixtape tables rather than each growing their own.
|
|
qbz-mixtape = { path = "../qbz-mixtape" }
|
|
qbz-source = { path = "../qbz-source" }
|
|
# The media-server protocol crates. `qbz-qt` needs them directly for the SYNC
|
|
# (which lives here, where the tokio runtime and the progress UI are) and for
|
|
# `Credentials`, which the settings glue derives.
|
|
qbz-jellyfin = { path = "../qbz-jellyfin" }
|
|
qbz-subsonic = { path = "../qbz-subsonic" }
|
|
qbz-media-cache = { path = "../qbz-media-cache" }
|
|
# Cast: Chromecast + DLNA discovery, the two device families and the local
|
|
# media server (src/cast_qt.rs drives it — nothing is reimplemented).
|
|
qbz-cast = { path = "../qbz-cast" }
|
|
qbz-plex = { path = "../qbz-plex" }
|
|
qbz-lyrics = { path = "../qbz-lyrics" }
|
|
# Playlist Importer: the provider scrapers (Spotify / Apple Music / Tidal /
|
|
# Deezer), the Qobuz matcher and the playlist creation. Headless and
|
|
# frontend-agnostic (ADR-006) — the same crate the Slint app uses
|
|
# (crates/qbz/Cargo.toml), so the two frontends share one importer instead of
|
|
# each growing its own. src/playlist_import_qt.rs drives it.
|
|
qbz-playlist-import = { path = "../qbz-playlist-import" }
|
|
qbz-text-utils = { path = "../qbz-text-utils" }
|
|
qbz-credentials = { path = "../qbz-credentials" }
|
|
qbz-i18n = { path = "../qbz-i18n" }
|
|
qbz-log = { path = "../qbz-log" }
|
|
qbz-theme = { path = "../qbz-theme" }
|
|
qbz-integrations = { path = "../qbz-integrations" }
|
|
# Last.fm "similar albums" row on the album page (external_reco_qt.rs).
|
|
qbz-external-reco = { path = "../qbz-external-reco" }
|
|
# System media controls — MPRIS on Linux (what makes the KDE Plasma / GNOME
|
|
# now-playing widget and the hardware media keys see QBZ), SMTC/MediaRemote
|
|
# elsewhere. Driven by src/media_controls_qt.rs.
|
|
#
|
|
# Plain, NOT target-gated, and with no features — the same line both other
|
|
# consumers use (crates/qbz/Cargo.toml:80, crates/qbzd/Cargo.toml:22). The
|
|
# crate carries its own cfg arms internally (qbz-media-controls/src/lib.rs:36-53)
|
|
# and is already a workspace member (crates/Cargo.toml:32), so this adds no
|
|
# lock entry.
|
|
#
|
|
# Its Linux backend rides mpris-server -> zbus 4 with its own current-thread
|
|
# tokio runtime, which is a DIFFERENT major from the zbus 5 that ksni / ashpd /
|
|
# rfd use with async-io. That is the only reason the two coexist in one
|
|
# process; the rule in the ksni comment below applies here too — never enable a
|
|
# `tokio` feature on any D-Bus dependency.
|
|
qbz-media-controls = { path = "../qbz-media-controls" }
|
|
# Qobuz Connect (2026-08-01 Qt-port contract): the shared session/renderer
|
|
# orchestration and the WS transport, consumed VERBATIM — backend untouchable,
|
|
# same edges as the Slint crate (crates/qbz/Cargo.toml:87-88). Driven by
|
|
# src/qconnect_transport_qt.rs + src/qconnect_engine_qt.rs (+ the B3/B4 facade
|
|
# and sink).
|
|
qconnect-app = { path = "../qconnect-app" }
|
|
qconnect-transport-ws = { path = "../qconnect-transport-ws" }
|
|
# Type-level edge only, sanctioned by contract §1.1: the QConnect renderer
|
|
# engine trait names `qbz_player::PlaybackState`, and the engine's feeder
|
|
# reaches the protected player seams THROUGH `runtime.core().player()` — the
|
|
# same edge the Slint crate has (crates/qbz/Cargo.toml:85). No
|
|
# qbz-player/qbz-audio internals are touched (TRACK-RULES §7).
|
|
qbz-player = { path = "../qbz-player" }
|
|
# QConnect: uuid v4 for controller action ids (the B3 facade / B4 sink — same
|
|
# as crates/qbz/Cargo.toml:90); futures-util for the engine's HTTP feeder
|
|
# (StreamExt over the response byte stream).
|
|
uuid = { version = "1", features = ["v4"] }
|
|
futures-util = { workspace = true }
|
|
async-trait = "0.1"
|
|
|
|
rusqlite = { version = "0.31", features = ["bundled"] }
|
|
image = { version = "0.25", default-features = false, features = ["jpeg", "png"] }
|
|
|
|
# Native file picker, used by exactly two call sites — the MyQBZ collection
|
|
# cover upload (src/myqbz_cover_qt.rs) and the My-QBZ branding icon picker
|
|
# (src/myqbz_prefs_qt.rs).
|
|
#
|
|
# The feature set is NOT negotiable and is copied verbatim from the reference
|
|
# (crates/qbz/Cargo.toml:72): "async-std" is rfd's name for the
|
|
# async-io/ashpd backend. NEVER enable rfd's "tokio" feature — zbus 5's
|
|
# tokio feature unifies graph-wide and breaks the accesskit/atspi/ksni
|
|
# threads. `default-features = false` keeps the GTK3 backend out; the
|
|
# xdg-portal path is the Wayland-correct one and needs no extra system libs.
|
|
rfd = { version = "0.15", default-features = false, features = ["xdg-portal", "async-std"] }
|
|
|
|
# Linux system tray via StatusNotifierItem (src/tray_linux.rs). Linux only —
|
|
# owner ruling K2: `crates/qbz/src/tray/macos.rs` exists because Slint's winit
|
|
# backend already bundles `muda`; under Qt that premise is gone, so macOS would
|
|
# be a NEW implementation, not a port. Windows has no tray in either tree.
|
|
#
|
|
# The comment below is copied verbatim from the reference
|
|
# (crates/qbz/Cargo.toml:139-143) because the feature set is not negotiable:
|
|
#
|
|
# default-features = false: ksni's default is "tokio", which flips the shared
|
|
# zbus 5 executor to tokio graph-wide (breaks accesskit/atspi plain-thread
|
|
# connections — see the rfd comment above). We only use the blocking API on
|
|
# worker threads; async-io self-drives the connection from any context.
|
|
#
|
|
# ksni 0.3.4 is already in the workspace lock (pulled by crates/qbz), so this
|
|
# adds no new lock entry. Feature unification is per-crate-graph, not
|
|
# per-target: ANY dependency that turns on zbus/tokio poisons the whole binary.
|
|
[target.'cfg(target_os = "linux")'.dependencies]
|
|
ksni = { version = "0.3", default-features = false, features = ["blocking", "async-io"] }
|
|
|
|
[build-dependencies]
|
|
cxx-qt-build = "0.7"
|
|
# A4 (Line Bed QQuickRhiItem): the crate's first hand-written C++
|
|
# (cxx/linebed_item.cpp). `qt-build-utils` provides moc + the Qt include
|
|
# paths — it is cxx-qt-build's OWN engine, already in the lock at the same
|
|
# version, so this adds no new package. `cc` compiles the static lib
|
|
# (likewise already in the lock).
|
|
qt-build-utils = "0.7"
|
|
cc = "1"
|
|
|
|
# macOS only: the Dock-icon activation policy behind the "Hide Dock icon when
|
|
# closed to menu bar" setting (Spotify behaviour — close puts the app in the
|
|
# menu bar and drops the Dock icon; minimize still goes to the Dock).
|
|
#
|
|
# objc2 0.5 / objc2-app-kit 0.2 are pinned to the SAME generation the Slint
|
|
# crate uses (crates/qbz/Cargo.toml:166-172). That matters even though this
|
|
# crate has no winit: mixing objc2 generations in one binary registers
|
|
# duplicate objc classes, which is the failure the reference documents at
|
|
# length. Only the two items `set_mac_dock_hidden` names are enabled.
|
|
#
|
|
# ⚠ NOT COMPILED OR TESTED HERE. This tree builds on Linux; the macOS arm is
|
|
# behind cfg, so `cargo build` cannot see it. First real check is a macOS
|
|
# build — the Mac mini (192.168.0.69) is where that happens.
|
|
[target.'cfg(target_os = "macos")'.dependencies]
|
|
# DIRECT objc2, added 2026-08-05 for the menu-bar tray: `declare_class!`,
|
|
# `msg_send_id!`, `sel!`, `ClassType`, `DeclaredClass`, `mutability` and
|
|
# `rc::Retained` all live at the objc2 root. Pinned to 0.5, the SAME
|
|
# generation objc2-app-kit 0.2 targets — mixing generations in one binary
|
|
# registers duplicate objc classes, which is the failure the reference
|
|
# documents at length (qbz/Cargo.toml:150-156).
|
|
objc2 = "0.5"
|
|
# `MainThreadMarker` — in THIS generation it is exported by objc2-foundation,
|
|
# not by the objc2 root (objc2-foundation-0.2.2/src/lib.rs:152, ungated). The
|
|
# reference imports it from the same place. There is no direct `objc2`
|
|
# dependency because nothing here names a type from it.
|
|
objc2-foundation = { version = "0.2", default-features = false, features = [
|
|
"std",
|
|
# NSThread gates `MainThreadMarker::new()` itself
|
|
# (objc2-foundation-0.2.2/src/thread.rs:210-213) — without it the type
|
|
# exists but only the UNSAFE `new_unchecked` does, which is not what this
|
|
# wants: the whole point is to ASK whether we are on the AppKit main
|
|
# thread and no-op if not. Caught by the Mac build; invisible on Linux.
|
|
"NSThread",
|
|
] }
|
|
objc2-app-kit = { version = "0.2", default-features = false, features = [
|
|
"std",
|
|
# NSApplication: the class and `setActivationPolicy`.
|
|
"NSApplication",
|
|
# NSResponder: `sharedApplication` is behind `cfg(feature = "NSResponder")`
|
|
# (objc2-app-kit-0.2.2/src/generated/NSApplication.rs:326-329) — it is
|
|
# NSApplication's superclass, and the NSApplication feature does NOT pull
|
|
# it.
|
|
"NSResponder",
|
|
# NSRunningApplication: where `NSApplicationActivationPolicy` actually
|
|
# lives (…/src/generated/NSRunningApplication.rs:33-35), NOT in
|
|
# NSApplication.rs as the name suggests. Verified by reading the crate
|
|
# rather than by compiling — this arm cannot be built on Linux, and the
|
|
# reference's own proven set (crates/qbz/Cargo.toml) lists the same three.
|
|
"NSRunningApplication",
|
|
# --- macos_chrome.rs (traffic-light centring) -------------------------
|
|
# Every one of these was read out of objc2-app-kit-0.2.2's own sources
|
|
# before being added, because this arm cannot be compiled on Linux:
|
|
# NSWindow — `NSApplication::mainWindow` is gated on it
|
|
# (generated/NSApplication.rs:355), and it carries `frame` +
|
|
# `standardWindowButton`. The style-mask / titlebar setters it also
|
|
# carries are NO LONGER USED: expanding the client area is Qt's job
|
|
# via the 6.9 window flags (see Main.qml), because QCocoaWindow
|
|
# recomputes and reassigns the mask behind us.
|
|
# NSButton + NSControl + NSView — `standardWindowButton` is gated on
|
|
# ALL THREE at once (generated/NSWindow.rs:1181), not just NSButton;
|
|
# NSControl is NSButton's superclass and NSView is NSControl's.
|
|
# NSView also provides the geometry the centring measures with
|
|
# (bounds / frame / setFrame / superview / isFlipped /
|
|
# convertRect_toView).
|
|
"NSWindow",
|
|
"NSButton",
|
|
"NSControl",
|
|
"NSView",
|
|
# --- tray_macos.rs (the menu-bar NSStatusItem) ------------------------
|
|
# Exactly the set the reference proves on a Mac (qbz/Cargo.toml:172-188);
|
|
# NSStatusBarButton is separate from NSStatusItem, and NSEvent is what
|
|
# `handle_status_click` reads to tell a right-click from a left-click.
|
|
"NSStatusBar",
|
|
"NSStatusItem",
|
|
"NSStatusBarButton",
|
|
"NSMenu",
|
|
"NSMenuItem",
|
|
"NSImage",
|
|
"NSEvent",
|
|
] }
|