Files
qbz/crates/qbz-qt/Cargo.toml
T
vicrodh 32fd77f75e feat(qt): Wallpaper follows the window on Plasma Wayland; its blurred twin drifts
"Wallpaper" now behaves like a window that sees straight through to the
desktop: it always paints the part of the wallpaper that lies under the
window, wherever the window is moved, with a stronger blur so nothing in
the picture competes with the text. X11, macOS and Windows already told
Qt where the window is. Wayland does not, so on KDE Plasma a tracker
reads the window's geometry from org_kde_plasma_window_management on its
own client connection and thread (wayland-client + the plasma protocol
crate, pure Rust backend). It follows this process's windows only,
drops every other window's object after its initial state, and
republishes only when the geometry list changes; the field picks the
window by size and ignores updates while another background is showing.
Where the protocol is not offered the crop stays centred and one line is
logged.

KWin hands that protocol only to a binary whose desktop entry names it by
an absolute Exec and lists it in X-KDE-Wayland-Interfaces, so the Linux
desktop entries now carry both (the deb/rpm /usr/bin/qbz symlink resolves
to the same binary). The AppImage step restores the bare Exec name that
linuxdeploy resolves inside the AppDir, and the Nix package rewrites Exec
to its store path.

"Wallpaper, blurred" is Blurred art with the wallpaper in the cover's
place, so it now drifts while the transport plays and holds still when it
does not.

cc
2026-09-14 05:22:55 -06:00

273 lines
13 KiB
TOML

[package]
name = "qbz-qt"
description = "Qt/QML frontend for QBZ (cxx-qt bridge over the UI-agnostic backend)"
version.workspace = true
edition.workspace = true
license.workspace = true
[[bin]]
# The shipped executable remains `qbz`; the package name stays `qbz-qt` so
# package-scoped Cargo commands remain unambiguous.
name = "qbz"
path = "src/main.rs"
[dependencies]
qbz-updater = { path = "../qbz-updater" }
qbz-control = { path = "../qbz-control" }
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"
# Deep links are validated by a real parse before they are acted on: a
# launcher can split a URL across argv, and a bare scheme is not a URL.
# Already in the workspace lock via qbz-models.
url = "2"
open = "5"
dirs = "5"
# Share links (src/share_qt.rs) copy through Qt's own QClipboard
# (cxx/clipboard.cpp) since #684: arboard's Wayland path needs the
# wlr/ext-data-control protocol, which Mutter lacks, and its X11 fallback is
# unreachable inside the Flatpak sandbox.
qbz-core = { path = "../qbz-core" }
qbz-app = { path = "../qbz-app" }
qbz-audio = { path = "../qbz-audio" }
# The HiFi Wizard's frontend-agnostic core; Qt owns the presentation adapter.
qbz-dac-wizard-core = { path = "../qbz-dac-wizard-core" }
qbz-qobuz = { path = "../qbz-qobuz" }
# Cross-provider "Open Music Link" resolver. The crate is frontend-agnostic;
# src/link_resolver_qt.rs supplies the live Qobuz search + Qt navigation seams.
qbz-music-link = { path = "../qbz-music-link" }
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), with the persistent
# library.db schema owned outside the Qt presentation layer.
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 playlist creation. The implementation stays
# frontend-agnostic (ADR-006); src/playlist_import_qt.rs drives it.
qbz-playlist-import = { path = "../qbz-playlist-import" }
qbz-account-migration = { path = "../qbz-account-migration" }
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 crate carries its own cfg
# arms internally and is already a workspace member.
#
# 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: shared session/renderer orchestration and WS transport,
# consumed without a frontend fork. Driven by src/qconnect_transport_qt.rs,
# src/qconnect_engine_qt.rs and the facade/event sink.
qconnect-app = { path = "../qconnect-app" }
qconnect-lan = { path = "../qconnect-lan" }
qconnect-protocol = { path = "../qconnect-protocol" }
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
# shared engine trait. 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);
# futures-util for the engine's HTTP feeder
# (StreamExt over the response byte stream).
uuid = { version = "1", features = ["v4"] }
futures-util = { workspace = true }
tempfile = "3"
async-trait = "0.1"
zeroize = "1"
rusqlite = { version = "0.40", 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).
#
# "async-std" is rfd's name for the async-io/ashpd backend. NEVER enable
# rfd's "tokio" feature: it would switch the shared zbus 5 executor choice.
# `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.17", default-features = false, features = ["xdg-portal"] }
# Linux system tray via StatusNotifierItem (src/tray_linux.rs). macOS and
# Windows use their native implementations in src/tray_macos.rs and
# src/tray_windows.rs.
#
# default-features = false: ksni's default is "tokio", which flips the shared
# zbus 5 executor to tokio graph-wide. We only use the blocking API on
# worker threads; async-io self-drives the connection from any context.
# Feature unification is per-crate-graph, not per-target: any dependency that
# turns on zbus/tokio changes the whole binary.
[target.'cfg(target_os = "linux")'.dependencies]
async-io = "2"
ksni = { version = "0.3", default-features = false, features = ["blocking", "async-io"] }
# Session-bus single-instance guard. Keep the same async-io executor as ksni;
# enabling zbus/tokio would poison the graph-wide executor choice.
zbus = { version = "5", default-features = false, features = ["async-io", "blocking-api"] }
# The "Wallpaper" background reads this window's place on a Plasma Wayland
# desktop through org_kde_plasma_window_management (wallpaper_wayland_qt.rs)
# on its OWN client connection; Qt's display is never shared.
wayland-client = "0.31"
wayland-protocols-plasma = { version = "0.3", features = ["client"] }
[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"
[target.'cfg(windows)'.build-dependencies]
# Icon + VERSIONINFO + the longPathAware manifest, embedded as PE resources.
# winresource, not winres: winres has been unmaintained since 2021 and this
# is its maintained fork.
winresource = "0.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 stay on one internally consistent generation;
# mixing objc2 generations in one binary can register duplicate Objective-C
# classes. Only the items used by the native tray/Dock integration 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 = "windows")'.dependencies]
# SetCurrentProcessExplicitAppUserModelID: the identity Windows groups the
# taskbar button under and matches a toast's app_id against. 0.61 is already
# in the workspace lock.
windows-sys = { version = "0.61", features = [
"Win32_UI_Shell",
# Single instance: CreateMutexW arbitrates, a named pipe carries the
# handoff, and both need the error codes to tell "already there" from
# "failed".
"Win32_Foundation",
"Win32_System_Threading",
"Win32_System_Pipes",
# ProcessIdToSessionId: the pipe name is qualified by session, because
# pipe names are machine-global while the mutex is per-session.
"Win32_System_RemoteDesktop",
"Win32_Storage_FileSystem",
# RegGetValueW: is this install's AppUserModelID actually registered?
# Without that, Windows silently drops every toast.
"Win32_System_Registry",
] }
[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 the generation targeted
# by objc2-app-kit 0.2.
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
# native integration imports it from here.
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",
] }