window.open() calls from social login providers are intercepted and opened
as related WebviewWindows (required on Linux/WebKitGTK). Both the main
window and all popups watch for play.qobuz.com?code_autorisation= so the
code is captured regardless of which window receives the final redirect.
Cleanup on timeout/cancel iterates all qobuz-oauth* windows.
- Save user_auth_token after successful OAuth login
- On bootstrap, restore session via saved token if no email/password creds
- If token is expired, clear it so user sees login screen normally
- Clear OAuth token on v2_clear_saved_credentials (logout)
- Add login_with_token() to api client (reuses OAuth /user/login step)
Also rename t->translateStore in NowPlayingBar.svelte (ADR-001 fix from Codex)
and improve clean-dev-runtime.sh to kill all stale processes and ports
Qobuz redirects to play.qobuz.com/discover?code_autorisation=XXX
not ?code=XXX. Both the navigation intercept and the /oauth/callback
API call were using the wrong parameter name, so the code was never
captured and the exchange request was malformed.
on_navigation returning false does not reliably block HTTP 302 redirects
on WebKitGTK (Linux). Closing the window from within the callback is
more reliable than calling close() from an async Tokio thread.
Changes:
- Always return true from on_navigation (never try to block)
- Close oauth window inside on_navigation callback after code capture
- Best-effort close via app.get_webview_window() after await (fallback)
- Add logging of every URL visited for debugging
- Idempotent: ignore duplicate code captures if callback fires twice
- v2_bulk_add_to_queue: add multiple tracks to end of queue
- v2_bulk_add_to_queue_next: add multiple tracks as play next (reversed
to preserve selection order)
- v2_bulk_add_favorites: add multiple tracks to favorites via API then
update local cache atomically (lock not held across await points)
- v2_add_tracks_to_playlist and v2_remove_tracks_from_playlist already
accept Vec<u64> — no changes needed for playlist bulk actions
Add get_all_custom_artist_images() database method and
v2_library_get_all_custom_artist_images command to support
loading all custom artist image overrides at once for the
global resolution store.
KWin scripting noBorder=false cannot override the xdg-decoration
protocol on Wayland. Window rules (kwinrulesrc) operate at a deeper
level and should force SSD before protocol negotiation. Uses
kwriteconfig6 to add/remove rules and qdbus6 to trigger reconfigure.
Now that the script is confirmed working (noBorder=false set on QBZ
window, resourceClass=qbz), remove the chatty per-window log lines
that would spam journalctl on every app launch.
The KWin D-Bus scripting API requires calling run() on the specific
script object (/Scripting/Script{id}) returned by loadScript(), not
start() on the /Scripting root. Also unloads previous script instance
before reloading, and adds 500ms delay to ensure window is mapped.
The KWin script checked for resourceClass === "qbz" but on Wayland,
KWin sets resourceClass from the xdg-toplevel app_id, which is the
full GTK application ID: "com.blitzfc.qbz" (from tauri.conf.json
identifier). Now matches all three possible values: com.blitzfc.qbz,
qbz, qbz-nix. Added diagnostic print() for journalctl debugging.
GTK3 hardcodes CLIENT_SIDE in the xdg-decoration Wayland protocol,
so GTK_CSD=0 alone doesn't work — KWin still sees CSD preference
and won't draw server-side decorations.
New approach for KDE Plasma + Wayland + system title bar:
1. Create window with decorations=false (no GTK CSD)
2. Load a KWin script via D-Bus that forces noBorder=false for QBZ
3. KWin draws its own native Breeze SSD title bar
This gives QBZ a title bar identical to Dolphin, Konsole, etc.
On non-KDE DEs, the old decorations(true) path is used unchanged.
Reverts the ineffective GTK_CSD=0 approach from previous commit.
Previously GTK_CSD=1 was set unconditionally on Wayland, forcing GTK to
draw its own client-side title bar (Breeze-GTK). On KDE Plasma this
looks wrong because native apps use KWin's server-side decorations
(Breeze-KDE).
Now reads the use_system_titlebar setting before GTK init:
- system title bar ON: GTK_CSD=0 (WM draws native decorations)
- system title bar OFF: GTK_CSD=1 (doesn't matter, decorations=false)
This makes QBZ's title bar match Dolphin, Konsole, etc. on KDE Plasma,
and Nautilus, etc. on GNOME — the window manager draws the decorations
in its own native style.
Instead of using wallpaper k-means + accent override (which produced
wrong backgrounds), the system source now reads ALL colors from the
DE config (kdeglobals for KDE, dconf hints for GNOME).
Maps Window/View/Button/Selection/Header sections directly to CSS
variables — bg-primary from Window, bg-secondary from View,
bg-tertiary from Button, accent from Selection, text from
ForegroundNormal, etc.
Adds SystemColorScheme type, get_system_color_scheme() in system.rs,
generate_theme_from_scheme() in generator.rs, and two new V2 commands:
v2_generate_theme_from_system_colors and v2_get_system_color_scheme.
Frontend tryAccentColorTheme() replaced with trySystemColorSchemeTheme()
which calls the new endpoint. Old color math helpers removed.
- V2 commands (generate_theme_from_image, generate_theme_from_wallpaper,
extract_palette) now use tokio::task::spawn_blocking to avoid freezing
the main thread during CPU-bound image decoding and k-means clustering.
This lets the UI spinner animate properly.
- KDE accent detection now falls back to Colors:Selection DecorationFocus
and BackgroundNormal from kdeglobals when no explicit AccentColor is set.
This reads the actual KDE color scheme accent instead of only looking
for the Plasma 6 custom accent field.
cc
Six new commands:
- v2_detect_desktop_environment: detect current DE
- v2_get_system_wallpaper: get wallpaper path
- v2_get_system_accent_color: get DE accent color
- v2_generate_theme_from_image: generate theme from any image
- v2_generate_theme_from_wallpaper: generate theme from system wallpaper
- v2_extract_palette: extract color palette for preview
All are pure functions (no State needed), registered in invoke_handler.
cc
Rust module auto_theme/ with:
- K-means clustering (k=6) on downsampled images for palette extraction
- CSS variable generator producing all ~40 required theme tokens
- WCAG AA contrast enforcement for text and accent colors
- Desktop environment detection (GNOME, KDE Plasma, COSMIC, XFCE, Cinnamon)
- Wallpaper path retrieval per DE (gsettings, Plasma config, COSMIC config)
- System accent color retrieval (GNOME 47+, KDE, COSMIC)
cc
Add get_all_custom_artist_images() database method and
v2_library_get_all_custom_artist_images command to support
loading all custom artist image overrides at once for the
global resolution store.
- Auto-detect installed .desktop filename (com.blitzfc.qbz.desktop,
qbz.desktop, qbz-nix.desktop) instead of hardcoding
- Run update-desktop-database after register/deregister so xdg-open
and browsers pick up the MIME association immediately
- Fix v2_check_qobuzapp_handler to match all known desktop filenames
- Align StartupWMClass to com.blitzfc.qbz across all packaging formats
- Add StartupNotify=true to Arch/AUR/Snap desktop files
Fixes browser URI handler not working for .deb users (Discussion #44)
The reload_settings() call at bootstrap is now gated behind a new
sync_audio_on_startup setting (default OFF). Most users don't need
it; it exists for edge cases where Player::new() holds stale settings
(e.g., after Flatpak updates changing DB schema).
Adds toggle in Settings > Audio with i18n for all 4 locales.
reload_settings() was running before every v2_play_track call, which
is excessive and can overwrite correct in-memory audio settings with
stale DB values (especially in Flatpak where DB sync timing differs).
Moved to runtime_bootstrap Step 6, after session activation when
per-user stores are ready. v2_reinit_audio_device still reloads on
explicit device changes.
On first play after app start, the audio thread held stale settings from
Player::new() — if the user had configured PipeWire/ALSA backend, the
thread might not use the correct backend/device, defaulting to 48kHz.
Now v2_play_track reloads settings from the DB before every play_data()
call, matching what v2_reinit_audio_device already does. This is why
re-selecting the device fixed the issue: it called reload_settings first.
Also extracts convert_to_qbz_audio_settings() helper to deduplicate the
settings conversion between v2_play_track and v2_reinit_audio_device.
Fixes#69
Add /radio/artist and /radio/track Qobuz API endpoints alongside the
existing /radio/album. TrackMenu now shows a radio submenu with QBZ
Radio (RadioPoolBuilder) and Qobuz Radio options when both are
available. Artist header radio button opens a dropdown to choose
between the two engines.
Backend: get_radio_artist(), get_radio_track() in client.rs,
v2_create_qobuz_artist_radio and v2_create_qobuz_track_radio commands.
Frontend: TrackMenu radio submenu with mutual exclusion against
share/download submenus, ArtistDetailView dropdown, LabelView support.
i18n keys added to all four locale files.
The /radio/album endpoint returns partial track objects without the
performer field, causing all tracks to show as "Unknown Artist".
Extract track IDs from the response and fetch each via get_track()
to get complete data, matching the pattern used by artist/track radio.
Add /radio/album Qobuz API endpoint support:
- RadioResponse struct in models.rs with lenient deserialization
- get_radio_album() client method
- v2_create_album_radio command that calls the API directly (no
RadioPoolBuilder/RadioDb), filters blacklisted artists, populates
queue, and sets Radio playback context
- Register command in lib.rs invoke handler
Two fixes for session not persisting:
1. Changed v2_get_all_queue_tracks to return a struct instead of a
tuple — Tauri IPC serialization of tuples is unreliable.
2. Added proactive session saves instead of relying on async
beforeunload (which races with window close):
- Debounced full save on track change (2s after new track starts)
- Debounced full save on pause
- Reduced periodic save from 60s to 15s during playback
- Close handler remains as best-effort backup
The save was broken in two ways:
1. Only current_track + upcoming (capped at 20) were saved, dropping
all history tracks. Queue was reconstructed incomplete on restore.
2. Track metadata (hires, bit_depth, sample_rate, source, etc.) was
not being persisted, only the basic 6 fields.
Add v2_get_all_queue_tracks command that returns the full uncapped
track list with correct current_index. Use it in saveSessionBeforeClose
instead of the capped v2_get_queue_state. Map all metadata fields.
Add last_view, view_context_id, view_context_type to player_state
table via migration. Update PersistedSession struct, save/load/clear,
and v2_save_session_state with 3 new optional parameters.
- Deezer/Spotify/Tidal: fetch metadata directly from platform APIs
(bypasses Odesli ~300ms vs ~2-3s) with Odesli as fallback
- Smart Qobuz search with 3 progressive attempts:
1. Full "title artist" query
2. Cleaned title (strip parenthetical/bracket suffixes like Remastered)
3. Artist-only search with fuzzy title matching
- Fixes albums not found when platform titles differ (e.g. Iron Maiden
"Senjutsu (2021 Remaster)" vs Qobuz "Senjutsu")
The Odesli API can fail with connection errors on rapid successive
requests (connection pool staleness). Add a single retry with 500ms
delay to handle transient network failures gracefully.
Also improve song.link URL handling to hint track vs album detection.
Odesli's API returns entity IDs as integers for some platforms (e.g.
Bandcamp) while others return strings. The Entity.id field was typed as
String which caused serde deserialization to fail on numeric values.
Add a custom deserializer that accepts both strings and numbers.
Also show actual backend error in LinkResolverModal for easier debugging.
Odesli does NOT include Qobuz in its linksByPlatform response, so the
previous approach of extracting platforms["qobuz"] would always fail.
New strategy: use Odesli to identify the content (title/artist), then
search the Qobuz catalog via CoreBridge to find the matching album or
track. Falls back from track search to album search if needed.