Commit Graph
100 Commits
Author SHA1 Message Date
Victor RH 0ce54c3f10 Merge pull request #222 from vicrodh/dependabot/npm_and_yarn/pre-release/lucide-svelte-1.0.1
chore(deps): bump lucide-svelte from 0.562.0 to 1.0.1
2026-03-30 13:47:23 -06:00
Victor RH 7db3ecfae9 Merge pull request #221 from vicrodh/dependabot/npm_and_yarn/pre-release/svelte-5.55.0
chore(deps-dev): bump svelte from 5.54.0 to 5.55.0
2026-03-30 13:31:08 -06:00
Victor RH 1b0ccd7e3e Merge pull request #220 from vicrodh/dependabot/cargo/src-tauri/pre-release/image-0.25.10
chore(deps): bump image from 0.25.9 to 0.25.10 in /src-tauri
2026-03-30 13:31:05 -06:00
Victor RH 69d6356746 Merge pull request #214 from vicrodh/dependabot/npm_and_yarn/picomatch-4.0.4
chore(deps-dev): bump picomatch from 4.0.3 to 4.0.4
2026-03-30 13:31:02 -06:00
Victor RH 2053b2e798 Merge pull request #230 from AdamArstall/bugfix/external/scroll-restoration
Fix scroll restoration for virtualized containers on back/forward navigation
2026-03-30 13:29:19 -06:00
Victor RH ae67d63899 Merge pull request #226 from GwendalBeaumont/refactor/external/i18n-interpolation
feat(i18n): add translation strings to views
2026-03-30 13:06:54 -06:00
Victor RH e6ea2b483c Merge pull request #198 from vicrodh/dependabot/cargo/src-tauri/rustls-webpki-0.103.10 2026-03-24 20:17:12 -06:00
Victor RH 3b2cd1addd Merge pull request #188 from vicrodh/dependabot/cargo/src-tauri/pre-release/qrcode-generator-5.0.0
chore(deps): bump qrcode-generator from 4.1.9 to 5.0.0 in /src-tauri
2026-03-19 17:24:26 -06:00
Victor RH 23811367af Merge pull request #186 from vicrodh/dependabot/npm_and_yarn/pre-release/svelte-check-4.4.5
chore(deps-dev): bump svelte-check from 4.3.5 to 4.4.5
2026-03-19 17:24:24 -06:00
Victor RH 2c06d1163b Merge pull request #189 from vicrodh/dependabot/npm_and_yarn/pre-release/sveltejs/vite-plugin-svelte-7.0.0
chore(deps-dev): bump @sveltejs/vite-plugin-svelte from 5.1.1 to 7.0.0
2026-03-19 17:21:08 -06:00
Victor RH ef4ee640b1 Merge pull request #191 from vicrodh/dependabot/cargo/src-tauri/pre-release/rcgen-0.14.7
chore(deps): bump rcgen from 0.12.1 to 0.14.7 in /src-tauri
2026-03-19 17:21:05 -06:00
Victor RH 9cf7657be7 Merge pull request #193 from vicrodh/dependabot/cargo/src-tauri/pre-release/ashpd-0.13.8
chore(deps): bump ashpd from 0.13.3 to 0.13.8 in /src-tauri
2026-03-19 17:21:03 -06:00
Victor RH eb82bc8920 Merge pull request #190 from vicrodh/dependabot/cargo/src-tauri/pre-release/tauri-2.10.3
chore(deps): bump tauri from 2.10.2 to 2.10.3 in /src-tauri
2026-03-19 17:21:01 -06:00
Victor RH 8adcd67702 Merge pull request #192 from vicrodh/dependabot/cargo/src-tauri/pre-release/uuid-1.22.0
chore(deps): bump uuid from 1.21.0 to 1.22.0 in /src-tauri
2026-03-19 17:20:58 -06:00
Victor RH 5ec74a5beb Merge pull request #184 from vicrodh/dependabot/npm_and_yarn/pre-release/typescript-5.9.3
chore(deps-dev): bump typescript from 5.6.3 to 5.9.3
2026-03-19 17:20:56 -06:00
Victor RH 38fab9f7c9 Merge pull request #187 from vicrodh/dependabot/npm_and_yarn/pre-release/vitest-4.1.0
chore(deps-dev): bump vitest from 4.0.17 to 4.1.0
2026-03-19 17:20:54 -06:00
Victor RH f5e7187251 Merge pull request #185 from vicrodh/dependabot/npm_and_yarn/pre-release/svelte-5.54.0
chore(deps-dev): bump svelte from 5.53.6 to 5.54.0
2026-03-19 17:20:50 -06:00
Victor RH ee1459a0f1 Update funding information in FUNDING.yml 2026-03-14 21:07:43 -06:00
vicrodh 616fab281d fix(flatpak): correct fabricated commit hash in screenshot URLs
The metainfo.xml had a fake full hash (cff32e5d47f3b44568af9...) that
returned 404 on raw.githubusercontent.com. Replace with the real commit
hash (cff32e5da7) where screenshots exist.
2026-03-04 00:31:05 -06:00
vicrodh d431581f16 fix(notifications): normalize ashpd artwork icon payload
Normalize notification artwork to square PNG (max 512x512, <=4 MiB) before sending Icon::Bytes via ashpd portal, so album art renders reliably without legacy fallback.\n\ncx
2026-03-03 23:31:31 -06:00
vicrodh 70adee9f16 fix(notifications): restore album artwork via native D-Bus path
The XDG portal notification spec's icon field doesn't support inline
album artwork the way freedesktop Notifications does with image-path.

Dual-path approach:
- Native (non-Flatpak): use org.freedesktop.Notifications D-Bus
  interface directly with image-path hint for album artwork
- Flatpak (sandboxed): use ashpd portal (Flathub requirement)

Uses ashpd::is_sandboxed() for detection and ashpd's re-exported
zbus for the direct D-Bus call, avoiding extra dependencies.
2026-03-03 22:46:43 -06:00
vicrodh d3cad99bd9 fix(notifications): send artwork as bytes instead of file URI
The XDG portal was not displaying album artwork when passed as a
file:// URI (showed KDE default icon instead). Send artwork as
Icon::Bytes so the portal receives the image data directly.
2026-03-03 22:41:08 -06:00
vicrodh 69aed4e1ad feat(notifications): migrate from notify-rust to ashpd XDG Portal
Replace direct D-Bus notifications (notify-rust) with XDG Desktop
Portal notifications (ashpd) for Flathub compliance. Portal
notifications use the app's .desktop file identity and don't require
--talk-name=org.freedesktop.Notifications permission.

- Replace notify-rust dep with ashpd (linux-only, tokio+notification)
- Use NotificationProxy for portal-based notifications
- Use stable ID "track-now-playing" to replace instead of stacking
- Convert artwork path to file:// URI via ashpd::Uri
- Graceful fallback when portal is unavailable
2026-03-03 22:25:59 -06:00
vicrodh b15a375e23 feat(immersive): add 3-mode background system (Full/Lite/Off)
Replace binary blur toggle with a 3-option dropdown. Full mode uses
the existing WebGL2 shader, Lite mode renders a pre-blurred static
image animated with CSS transforms (compositor-driven, immune to
Wayland frame callback stutter), and Off mode shows a solid color.

Includes config migration from disableBlurBackground to backgroundMode,
updated composition profiles, and i18n for all 4 locales.

Closes #127
2026-03-02 13:18:55 -06:00
vicrodh 00675de6b8 feat(snap): add snap sandbox detection and welcome modal wiring
Detect when QBZ runs inside a Snap sandbox (via SNAP env var) and show
a one-time welcome modal with required plug connection commands. Adds a
Snap Sandbox section in Settings with copyable commands, mirroring the
existing Flatpak pattern.

Backend: snap.rs module, DB table snap_welcome_shown, V2 commands.
Frontend: updatesService, +page.svelte modal queue, SettingsView section.
2026-03-02 11:10:50 -06:00
vicrodh 8529477778 feat(immersive): polish tunnel depth and artwork-driven line palette
cx
2026-03-02 01:06:55 -06:00
vicrodh 5930f599d6 feat(immersive): refine tunnel curve occlusion behavior
cx
2026-03-02 00:55:38 -06:00
vicrodh 5492e8115e feat(immersive): refine neon and tunnel panel visuals
cx
2026-03-02 00:50:45 -06:00
vicrodh 47f80149d5 feat(playlists): add custom artwork and folder sidebar visibility controls
cx
2026-03-01 22:09:43 -06:00
vicrodh ec3ecb35b2 fix(flatpak): restore pipewire stream matching and tray icon access
cx
2026-03-01 20:18:17 -06:00
vicrodh afe086e6fd fix(ci): stabilize rc releases and version flatpak artifact
cx
2026-03-01 19:34:24 -06:00
vicrodh 0f28fd8979 fix: add mpris plug to root snapcraft.yaml
The packaging/snap/ copy already had the mpris plug but the root
snapcraft.yaml was missing it, preventing MPRIS media controls
from working in the Snap package.
2026-03-01 19:07:00 -06:00
vicrodh cb4708770c fix: sync local flatpak manifest with flathub PR changes
Remove broad filesystem permissions, add PipeWire :ro access,
drop --own-name (auto-granted since MPRIS name contains appid).
2026-03-01 19:06:25 -06:00
vicrodh b49d0b3e3b chore(release): bump version to 1.1.19-rc-1
cx
2026-03-01 18:59:24 -06:00
vicrodh 55a1ef25e6 fix(ui): polish mini/immersive layout and queue favorite behavior
cx
2026-03-01 18:56:23 -06:00
vicrodh 9c6265abd8 fix(linux): default main window to opaque for shutdown stability
cx
2026-03-01 17:21:49 -06:00
vicrodh 75254d26a9 fix(linux): apply WebKit EGL shutdown stability workarounds
cx
2026-03-01 17:13:42 -06:00
vicrodh b95edec754 fix(shutdown): avoid forced exit(0) during main window close
cx
2026-03-01 16:55:16 -06:00
vicrodh d72eac3c1e fix: search album/artist grid collapses to single column on keystroke
When typing in the search bar, new results cause the virtual scroll
container to remount. The ResizeObserver was stale (watching the old
destroyed element) and containerWidth reset to 0, causing gridCols
to default to 1. Two fixes: cache the last known column count so
width=0 uses the previous layout, and reconnect the ResizeObserver
whenever the container element changes.
2026-03-01 16:38:38 -06:00
vicrodh 63521e920b fix: Escape on empty search input blurs focus
First Escape clears the search text, second Escape when input is
already empty removes focus from the input. Applied to both the
sidebar and titlebar search inputs for keyboard-driven navigation.
2026-03-01 16:33:33 -06:00
vicrodh eabeb3959f fix: move multi-select button to header in WeeklySuggestView
The CheckSquare button was next to the search bar instead of in the
header actions alongside Play/Shuffle/Save, inconsistent with DailyQ,
FavQ and TopQ views. Moved it to the header and removed orphaned CSS.
2026-03-01 16:22:26 -06:00
vicrodh 7753d2520e fix: multi-select add to playlist sends all tracks in one call
The broken views called onTrackAddToPlaylist in a loop (once per
selected track), causing each call to overwrite the previous track
IDs in the playlist modal. Only the last track survived. Now all 6
views collect IDs first and call onBulkAddToPlaylist once with the
full array.

Views fixed: ArtistDetailView, AlbumDetailView, LabelView,
FavoritesView, WeeklySuggestView, PlaylistDetailView.
2026-03-01 16:19:12 -06:00
vicrodh 1054e71768 fix: populate downloaded_format_ids in all purchase endpoints
v2_purchases_get_all and v2_purchases_get_by_type only set the
downloaded boolean but never populated downloaded_format_ids, so
check marks disappeared after app restart. Now both use
get_downloaded_purchase_formats() and build the format map, matching
the behavior already present in v2_purchases_get_album.

Fixes #119
2026-03-01 16:11:04 -06:00
vicrodh f9e9d58e81 fix: add-to-library uses album folder instead of root destination
When downloading a purchased album to ~/Music, the "Add to Local
Library" button was adding ~/Music (the entire music directory) instead
of the specific album folder like ~/Music/Artist/Album/.

After the first track downloads, extract the album-level folder from
the returned file path and update the stored destination. This ensures
only the downloaded album gets added to the library. Fixes #117.
2026-03-01 15:47:01 -06:00
vicrodh 683f97a5d5 fix: deduplicate albums with quality subfolders in local library
The library scanner's album_root_dir() only knew to skip disc folders
(Disc 1, CD2, etc.) when resolving the album directory. Files stored
in quality subfolders like artist/album/FLAC 24-bit - 96 kHz/track.flac
caused each encoding variant to appear as a separate album card.

Add is_encoding_folder() to detect common audio format directory names
(FLAC, MP3, AAC, WAV, etc.) and make album_root_dir() skip past both
encoding and disc subdirectories, handling chained nesting too.

Fixes the workaround from #116.
2026-03-01 15:40:22 -06:00
vicrodh c2f2888312 feat: add cancel button for purchase album downloads
Allow users to cancel an in-progress album download. The current track
finishes but no further tracks are started. A cancel button appears in
the progress bar during download, and a cancelled state shows how many
tracks completed. Closes #133.
2026-03-01 14:14:47 -06:00
vicrodh 8fef723ced fix: eliminate dead space in Customize Shortcuts modal
Switch from CSS grid to CSS multi-column layout so categories pack
tightly top-to-bottom instead of creating large empty rows when
columns have uneven heights.
2026-03-01 14:09:17 -06:00
vicrodh 976e5226b4 feat: add miniplayer keyboard shortcuts and fix X button behavior
Add Shift+M toggle shortcut for mini player mode (main and mini window).
Add 1-5 number keys for surface switching (micro/compact/artwork/queue/lyrics)
in mini player context. Add playback shortcuts (Space, Ctrl+Arrow) to mini
player window. Fix X button to close app (or hide to tray) instead of
duplicating the Expand button behavior. Closes #134.
2026-03-01 14:04:18 -06:00
vicrodh 0f1f6b3716 fix: amplify ambient shader motion for visible animation
Previous shader was too subtle — drift, rotation, and breathing were
imperceptible with the smooth 8x8 texture. Now with much larger values:
- Drift: 0.05 -> 0.20 max (Lissajous with two frequencies)
- Scale breathing: 2% -> 12% range
- Rotation: 0.86deg -> 5deg range
- Added warm/cool color shifting over time
- Stronger vignette pulsing and brightness breathing

Safe because the 8x8-sourced texture has zero structure to reveal.
2026-03-01 13:30:18 -06:00
vicrodh db5d9182f2 fix: use 8x8 downscale instead of CSS blur to destroy artwork structure
CSS filter:blur() may not work reliably in all WebView environments,
leaving artwork structure visible as rectangular artifacts. New
approach: draw the full artwork at 8x8 resolution (64 pixels) where
no structure can physically survive, then scale up to 128x128 with
high-quality bilinear smoothing. One optional blur pass for polish.
2026-03-01 13:29:48 -06:00
vicrodh e5955a8f91 fix: replace multi-layer ambient shader with single-layer approach
The 3-layer parallax shader created visible rectangular artifacts on
dark/uniform artwork. Each layer sampled at different scale/offset,
and the spatial weighting transitions between layers showed as
geometric seams regardless of offset magnitude.

New shader uses a single texture sample with organic Lissajous drift,
gentle rotation, scale breathing, pulsing vignette, and vertical tint.
The pre-blurred texture handles the visual complexity — the shader
just adds subtle life without geometric artifacts.
2026-03-01 13:24:30 -06:00
vicrodh 10d395a89e fix: preserve dark artwork mood in ambient background
Two issues caused visible dark rectangles on simple/dark artwork:

1. Texture normalization always mapped to [30-240] regardless of source
   brightness. For dark artwork (Metallica), this amplified noise 5x and
   turned a dark album into a light gray background. Now uses
   brightness-aware blending: dark artwork gets gentle lift (preserves
   mood), colorful artwork gets full normalization.

2. Shader layer offsets were too large (up to 20% of texture), causing
   visible layer boundaries where each sampling layer showed different
   parts of the amplified texture. Reduced offsets by ~60% and tightened
   scale ranges for smoother parallax.
2026-03-01 13:22:05 -06:00
vicrodh 23aefcda80 fix: replace CLAMP_TO_EDGE with MIRRORED_REPEAT for ambient texture
CLAMP_TO_EDGE stretched edge pixels when ambient shader UVs exceeded
[0,1] range, creating visible dark rectangles on simple/dark artwork
(e.g. Metallica's black album). MIRRORED_REPEAT seamlessly mirrors
the texture instead. Also removed the hard clamp() in the fragment
shader so the mirrored wrapping actually takes effect.
2026-03-01 13:15:33 -06:00
vicrodh ef48cd5936 refactor: move immersive settings to end of Appearance section
Group immersive-related settings under a new "Immersive" subsection
at the bottom of Settings > Appearance, in order:
1. Immersive Default View
2. Composition (collapsible)
3. Disable Blur Background
4. Animation Frame Rate (collapsible)

Also fix chevron alignment in collapsible sections to be right-aligned
(justify-content: space-between) and clean up unused CSS classes.
2026-03-01 13:03:12 -06:00
vicrodh 298d669c58 fix: align settings UI elements with established patterns
- Replace native <select> with Dropdown component for GSK renderer
- Fix collapsible section layout: title+chevron on one row, description below
- Apply section-title-row wrapper to both Animation Frame Rate and Composition
- Add divider between Animation Frame Rate and Mini Player Default View
- Remove unused .composition-select CSS
2026-03-01 12:55:34 -06:00
vicrodh c518cffe30 fix: align immersive settings with standard collapsible pattern
- Replace raw checkbox with Toggle component for blur background
- Use composition-subsection class for FPS section (adds top border)
- Use composition-title class for consistent 14px font size
- Move desc to section-note inside expanded content
- Add summary key for inline collapsed text
- Shorten title from "Immersive Animation Frame Rate" to
  "Animation Frame Rate"
2026-03-01 12:46:18 -06:00
vicrodh d023af5478 fix: align immersive FPS collapse with other collapsible sections
Replace raw text caret and h4 with the standard section-title +
section-summary + ChevronDown/Up icon pattern used by the composition
section. Remove duplicate description that now shows as summary.
2026-03-01 12:40:48 -06:00
vicrodh d509c30979 feat: update composition profiles with GSK renderer and blur toggle
Add gskRenderer and disableBlurBackground fields to CompositionProfile.
Update existing profiles: Xwayland Performance now disables blur.
Add new Max Performance profile (Cairo software renderer + blur off)
for maximum compatibility on stutter-affected systems.

Profile matching and apply logic now includes all new fields.

Part of #127.
2026-03-01 12:39:47 -06:00
vicrodh 7d8e8fadcf feat: add qbz --autoconfig-graphics diagnostic CLI tool
Detects GPU (NVIDIA/AMD/Intel), display server (Wayland/X11), desktop
environment, and VM status. Computes optimal configuration based on a
decision matrix and writes it to the graphics settings DB.

Usage: qbz --autoconfig-graphics

Part of #127.
2026-03-01 12:37:16 -06:00
vicrodh 09b1ee0d72 feat: add option to disable immersive blur background
When enabled, replaces the expensive blurred album artwork background
with a solid dark color extracted from the artwork at tiny resolution.
Significantly reduces GPU load and fixes stutter on affected systems.

Setting is stored in localStorage (same as FPS settings) and applies
on next immersive mode entry.

Part of #127.
2026-03-01 12:34:46 -06:00
vicrodh 58ad252798 feat: add GSK_RENDERER control for GTK4 rendering backend
Add gsk_renderer field to graphics settings DB with UI dropdown
(Auto/GL/NGL/Vulkan/Cairo). Value is read at startup and set as
GSK_RENDERER env var. Env var override takes precedence. Also
included in --reset-graphics and reset now restores hw_accel to true.

Part of #127.
2026-03-01 12:30:41 -06:00
vicrodh 82374ffde6 feat: add AMD and Intel GPU detection
Add is_amd_gpu() and is_intel_gpu() detection functions alongside
existing is_nvidia_gpu(). Update GraphicsStartupStatus to expose
has_amd and has_intel to frontend. Log detected GPUs at startup.

Part of #127.
2026-03-01 11:51:37 -06:00
vicrodh 1e98fa631e fix: wire hardware_acceleration DB value to startup logic
The hardware_acceleration toggle in settings was dead code — stored in
DB but ignored at startup (main.rs only checked QBZ_HARDWARE_ACCEL env
var). Now the DB value is read as the default, with env var override.

Fixes part of #127.
2026-03-01 11:49:51 -06:00
vicrodh 8e104a3f3e fix(audio): replace deprecated CPAL name() usage with description()
cx
2026-03-01 10:44:54 -06:00
vicrodh 0f642d0176 merge: sync security fixes from pre-release 2026-03-01 09:53:18 -06:00
vicrodh 4a97ff5588 fix(security): remove deterministic fallback inputs from credential key derivation
cx
2026-03-01 09:51:09 -06:00
vicrodh b57c7e38dc fix(security): remove unused tauri api user_id accessor
cx
2026-03-01 09:50:06 -06:00
vicrodh c17c01b02b fix(security): remove unused qobuz user_id accessor
cx
2026-03-01 09:49:57 -06:00
vicrodh cd6be78969 merge: sync pre-release into main 2026-03-01 09:31:16 -06:00
vicrodh af332a5433 fix(security): remove hardcoded key material from credential encryption
cx
2026-03-01 09:24:47 -06:00
vicrodh 133defbafa fix(security): redact sensitive identifiers and secrets from backend logs
cx
2026-03-01 09:24:42 -06:00
vicrodh 86450f629d fix(security): use cryptographically secure session id generation
cx
2026-03-01 09:20:39 -06:00
vicrodh 426d0ea3ff fix(security): replace regex HTML sanitizer with DOM-based sanitization
cx
2026-03-01 09:20:21 -06:00
vicrodh ead36545d3 fix(actions): harden release-aur workflow permissions and input handling
cx
2026-03-01 09:19:37 -06:00
vicrodh 87fd58b5ec fix(shutdown): avoid manual secondary window teardown on app close
cx
2026-03-01 09:13:40 -06:00
vicrodh 2095e5fce3 fix: add FavQ and TopQ context types to playback context
The ContextType enum and v2_set_playback_context match were missing
fav_q and top_q variants, causing an "Invalid context type" error that
blocked all playback from FavQ and TopQ views. Also wrap
setPlaybackContext calls in try/catch so context errors don't prevent
track playback.

Fixes #140
2026-03-01 08:58:45 -06:00
vicrodh ecbbcbbf98 fix: prevent double-close of WebKit windows causing heap corruption on exit
The "free(): corrupted unsorted chunks" crash was caused by secondary
windows (miniplayer) being closed twice during shutdown:

1. CloseRequested handler closes secondary windows before main closes
2. RunEvent::Exit handler tried to close them again

Calling close() on a window already mid-teardown causes WebKit's
EGL/GPU thread to hit a double-free during TLS cleanup, corrupting
the heap. This was visible even without audio playing.

Fix: use an AtomicBool flag (WINDOWS_CLOSED_BY_USER) set in
CloseRequested, checked in RunEvent::Exit. Secondary windows are
only closed in Exit if CloseRequested didn't already handle them
(e.g., SIGTERM from tauri dev hot reload).
2026-02-28 18:04:49 -06:00
vicrodh 421f993f82 fix: clamp window size to screen resolution and fix physical/logical pixel mismatch
Two fixes for issue #139 (window opens larger than screen):

1. Screen resolution clamping at startup: detect monitor size via
   xdpyinfo/xrandr and clamp saved window dimensions to 95% of screen.
   Prevents windows from opening larger than the display even if the DB
   has valid-but-too-large values.

2. Physical-to-logical pixel conversion in resize handler: the Resized
   event gives physical pixels but inner_size() expects logical pixels.
   With scale factor > 1.0, the window would grow each restart because
   physical values were saved and restored as logical. Now divides by
   scale_factor before persisting.

Closes #139
2026-02-28 17:56:42 -06:00
vicrodh 37df510994 feat: add quality-adjusted tooltip case for hardware compatibility
When QBZ adjusts quality for device compatibility (e.g. requesting
Hi-Res instead of Ultra Hi-Res because the DAC doesn't support
176.4kHz), show a blue info tooltip instead of the yellow "Resampled
by system" warning. The new message clarifies that playback is
bit-perfect with no resampling — QBZ simply requested a compatible
quality from the source.

Two distinct tooltip cases:
- Yellow: "Resampled by system" (PipeWire/system is resampling)
- Blue: "Quality adjusted for device compatibility" (QBZ chose lower
  quality, hardware plays natively)
2026-02-28 07:48:03 -06:00
vicrodh 56b0e562a1 fix: check hardware rate compatibility on cached audio before playing
The smart quality downgrade was only in the network fetch path, but
tracks served from cache (memory L1, disk L2, offline) bypassed it
entirely. Now all cache paths probe the FLAC header for sample rate
and skip the cache if the hardware doesn't support it, falling through
to the network path which re-requests at Hi-Res quality.

Also applies quality downgrade to prefetch so upcoming tracks are
fetched at compatible quality instead of caching incompatible data.
2026-02-28 07:48:03 -06:00
vicrodh 7086110569 feat: smart quality downgrade for unsupported sample rates
When ALSA backend detects hardware doesn't support a track's sample rate
(e.g. 176400Hz on a DAC that supports up to 192000Hz but not 176.4kHz),
automatically re-request the track from Qobuz at Hi-Res quality instead
of Ultra Hi-Res. This avoids resampling and maintains bit-perfect playback
by getting a version at a rate the hardware natively supports.

Adds device_supports_sample_rate() and get_device_supported_rates()
public API to qbz-audio for hardware capability queries via /proc/asound.
2026-02-28 07:48:03 -06:00
vicrodh 3329d1d632 fix: suspend PipeWire before CPAL stream in exclusive mode
When transitioning from ALSA Direct to CPAL fallback (e.g. for rate
resampling), PipeWire auto-re-acquires the device after ALSA Direct
releases it. CPAL then can't open the device.

Suspend PipeWire sinks before creating CPAL stream in exclusive mode,
same pattern used in ALSA Direct DeviceBusy handler.
2026-02-28 07:48:03 -06:00
vicrodh b234828228 fix: pre-check hardware rates via /proc/asound before ALSA Direct
When ALSA Direct opens a PCM device and fails to set the rate, the
USB DAC doesn't release the device fast enough for CPAL to open it
afterwards. This caused "device busy" errors even with a delay.

New approach: read /proc/asound/cardN/stream0 to check supported
rates BEFORE opening the device. If the rate isn't supported, skip
ALSA Direct entirely and fall through to CPAL (which has rate
fallback logic). The device is never touched at the wrong rate.
2026-02-28 07:48:03 -06:00
vicrodh d667ace987 fix: add 100ms delay after ALSA Direct PCM release before CPAL fallback
When ALSA Direct opens a PCM device and fails to configure the rate,
the PCM handle is dropped but the kernel may not release the device
instantly. CPAL then fails with "Device busy" when trying to open it.

Add 100ms sleep after InvalidParams to let the kernel fully release
the device before CPAL attempts to open it. Also fix error messages
to show the effective (fallback) rate instead of the original rate.
2026-02-28 07:48:03 -06:00
vicrodh c99f0d342d fix: add sample rate fallback to ALSA backend CPAL path
When ALSA Direct can't handle a rate (e.g. 176.4kHz) and falls back
to CPAL, CPAL also fails because the hardware doesn't support it.

Port find_best_fallback_rate from PipeWire backend: queries supported
rates via CPAL, picks highest rate in same family (44.1k or 48k)
that's <= requested. 176.4kHz on a 96kHz-max DAC now falls back to
88.2kHz and rodio resamples, instead of failing entirely.
2026-02-28 07:48:03 -06:00
vicrodh 29343e8149 fix: fall back to CPAL when ALSA Direct doesn't support sample rate
When hardware doesn't natively support a sample rate (e.g. 176.4kHz),
return None from try_create_direct_stream instead of a hard error.
This lets the player fall through to CPAL/rodio which can resample.

Applied to both hw and plughw paths — InvalidParams on rate now
triggers graceful fallback instead of killing playback.
2026-02-28 07:48:03 -06:00
vicrodh aff45f98f0 fix: close secondary windows before main to allow app exit, hide miniplayer on tray
Previous commit removed the miniplayer close from CloseRequested,
but that kept the app alive because Tauri won't exit while windows
remain open. Now closes secondary windows with close() (not
destroy()) in CloseRequested before the main window closes.
Also hides the miniplayer when close-to-tray activates.
2026-02-28 07:48:03 -06:00
vicrodh b1e4f8db94 fix: prevent WebKit crash on close by eliminating double-destroy of miniplayer
The miniplayer window was being destroyed twice during shutdown:
once in CloseRequested and again in RunEvent::Exit. This caused
WebKit's TLS destructors to collide during process teardown,
resulting in SIGABRT in WebKitWebProcess.

Changes:
- Remove miniplayer destroy from CloseRequested (was premature)
- Guard global on_window_event to only handle main window
- In RunEvent::Exit, close all secondary windows (miniplayer, oauth)
  with close() instead of destroy() for graceful WebKit cleanup
- Add 100ms delay after closing secondary windows to let WebKit
  tear down EGL/GPU resources before process exit
2026-02-28 07:48:03 -06:00
vicrodh 21fc1dfea3 fix: validate window size on load to prevent GDK crash from corrupt DB values
Extends PR #136 (save-time validation) with load-time validation.
If the DB contains corrupt sizes (e.g. 9084748x62267212), get_settings()
now returns safe defaults instead of passing garbage to GDK/Cairo.
Also extracts validation into a shared helper and adds NaN/Inf checks.
2026-02-28 07:48:03 -06:00
vicrodh fe3c5b0528 fix: suspend PipeWire sink before ALSA Direct to prevent DeviceBusy on first play
When switching from PipeWire to ALSA Direct, PipeWire holds the ALSA
device open causing EBUSY on the first snd_pcm_open. Now detects
DeviceBusy, suspends the PipeWire sink via pactl, waits 200ms for
release, then retries. Applied to both hw and plughw paths.
2026-02-28 07:48:03 -06:00
vicrodh 199f19a426 fix: read hardware audio status from V2 player instead of legacy
v2_get_hardware_audio_status was reading sample_rate/bit_depth from the
legacy player state, which is never updated since playback runs through
the V2 player (CoreBridge). Now reads from V2 player first, falls back
to legacy only if CoreBridge isn't initialized yet.
2026-02-28 07:48:03 -06:00
vicrodh cd4685820e fix: ALSA backend unavailable due to cpal 0.17 HostId::name() case change
cpal 0.15 returned "ALSA" from HostId::name(), cpal 0.17 returns "Alsa"
(now uses stringify! on the enum variant). Use eq_ignore_ascii_case()
for the comparison so it works regardless of cpal version.
2026-02-28 07:48:03 -06:00
vicrodh 7f6ac9bae6 fix: add sample rate fallback when DAC doesn't support requested rate
Query the DAC's supported sample rates from /proc/asound/cardN/stream0
(maps PipeWire sink to ALSA card via pactl). If the track's native rate
isn't supported (e.g., 176.4kHz), fall back to the nearest rate in the
same family (e.g., 88.2kHz). Rodio resamples from track rate to stream
rate automatically, so audio plays instead of producing silence.
2026-02-28 07:48:03 -06:00
vicrodh e1d006e672 fix: sync audio settings on startup for OAuth login path
The OAuth login path in runtime_bootstrap was missing the audio settings
sync code that exists in the email/password path. Users logging in via
OAuth had backend_type remain None, causing legacy CPAL fallback and
requiring manual settings reconfiguration on every startup.
2026-02-28 07:48:03 -06:00
vicrodh 5ef58f6121 fix: use with_buffer_size() after with_supported_config() to prevent underruns
rodio 0.22's with_supported_config() resets buffer_size to Default via
..Default::default(), so our BufferSize settings in StreamConfig were
never reaching CPAL. MixerDeviceSink has zero internal buffering, making
CPAL's buffer the ONLY buffer between the mixer and audio hardware.

Chain .with_buffer_size(Fixed(sample_rate/10)) AFTER with_supported_config()
to actually pass ~100ms buffer to CPAL, matching old vendored cpal behavior.
2026-02-28 07:48:03 -06:00
vicrodh b054f51410 fix: revert BufferSize::Fixed for PipeWire, use BufferSize::Default
PipeWire manages its own buffer scheduling via quantum. Forcing a fixed
buffer size (sample_rate / 10 = 19200 frames at 192kHz) conflicts with
PipeWire's internal scheduling and causes buffer underruns that produce
audible noise.

BufferSize::Fixed is kept only for:
- Exclusive mode (512 frames, low latency)
- ALSA backend (direct ALSA, not through PipeWire)
2026-02-28 07:48:03 -06:00
vicrodh 014becfb02 fix: disable PipeWire clock.force-quantum for rodio 0.22 compatibility
rodio 0.22's MixerDeviceSink has an internal mixer thread that cannot
synchronize with PipeWire's forced quantum scheduling. This caused
massive buffer underruns (600+) at sample rates >= 88.2kHz when Force
Bit Perfect was enabled.

clock.force-rate alone is sufficient for bit-perfect sample rate
switching. Logs confirm zero underruns at 88.2kHz and minimal underruns
at 192kHz (only during heavy I/O) when quantum is not forced.
2026-02-28 07:48:03 -06:00
vicrodh 3d8733181a chore: remove unused SampleRate imports
SampleRate is now a u32 type alias in cpal 0.17, so the import is no
longer needed anywhere.
2026-02-28 07:48:03 -06:00
vicrodh 6e8b8410c9 fix: set explicit 100ms buffer size to replace vendored cpal tuning
Replace BufferSize::Default with BufferSize::Fixed(sample_rate / 10)
for non-exclusive mode. This preserves the 100ms period tuning that was
previously applied in the vendored cpal ALSA backend, reducing CPU
wakeups during playback.
2026-02-28 07:48:03 -06:00
vicrodh b5ec5de785 refactor: migrate all code to rodio 0.22 API
- OutputStream/OutputStreamHandle -> DeviceSinkBuilder/MixerDeviceSink
- Sink -> Player (as RodioPlayer)
- current_frame_len -> current_span_len in all Source impls
- Source::channels() returns NonZero<u16>, sample_rate() returns NonZero<u32>
- SamplesBuffer no longer generic, constructor takes NonZero args
- SampleRate is now u32 type alias (no tuple struct)
- Decoder::new_mp4 no longer takes Mp4Type hint
- Applied across both crates/ workspace and src-tauri/ legacy code
2026-02-28 07:48:03 -06:00