feat(dac-wizard): add DAC capabilities query from PipeWire/PulseAudio
Backend (Rust): - Add query_dac_capabilities command that queries pw-cli and pactl - Returns supported sample rates, formats, channels, and device description - Parses both PipeWire and PulseAudio outputs for maximum compatibility Frontend (Svelte): - Add "Query DAC Capabilities" button in detect-dac step - Display detected sample rates, formats, channels - Show disclaimer about checking manufacturer docs if values don't match - Add translations for query UI (EN/ES/FR/DE)
This commit is contained in:
1 parent
5ce5a57d5b
commit
278136762d
7 files changed
+466
-1
No files matched your search
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { X, CheckCircle, Check, AlertTriangle, XCircle } from 'lucide-svelte';
|
||||
import { X, CheckCircle, Check, AlertTriangle, XCircle, Search, Loader2 } from 'lucide-svelte';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { t } from '$lib/i18n';
|
||||
import WizardStepper, { type Step } from './wizard/WizardStepper.svelte';
|
||||
import CommandBlock from './wizard/CommandBlock.svelte';
|
||||
@@ -7,6 +8,16 @@
|
||||
import DistroSelector from './wizard/DistroSelector.svelte';
|
||||
import BitPerfectAppSelector from './wizard/BitPerfectAppSelector.svelte';
|
||||
|
||||
// DAC capabilities from Tauri backend
|
||||
interface DacCapabilities {
|
||||
node_name: string;
|
||||
sample_rates: number[];
|
||||
formats: string[];
|
||||
channels: number | null;
|
||||
description: string | null;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
// DAC node name validation
|
||||
type DacType = 'usb' | 'pci' | 'bluetooth' | 'virtual' | 'unknown';
|
||||
type ValidationStatus = 'empty' | 'valid' | 'invalid';
|
||||
@@ -71,6 +82,36 @@
|
||||
let showRollback = $state(false);
|
||||
let selectedDistro = $state('debian');
|
||||
|
||||
// DAC capabilities query state
|
||||
let dacCapabilities = $state<DacCapabilities | null>(null);
|
||||
let isQueryingDac = $state(false);
|
||||
|
||||
async function queryDacCapabilities() {
|
||||
if (!dacNodeName.trim() || dacValidation !== 'valid') return;
|
||||
|
||||
isQueryingDac = true;
|
||||
dacCapabilities = null;
|
||||
|
||||
try {
|
||||
const caps = await invoke<DacCapabilities>('query_dac_capabilities', {
|
||||
nodeName: dacNodeName
|
||||
});
|
||||
dacCapabilities = caps;
|
||||
} catch (err) {
|
||||
console.error('Failed to query DAC capabilities:', err);
|
||||
dacCapabilities = {
|
||||
node_name: dacNodeName,
|
||||
sample_rates: [],
|
||||
formats: [],
|
||||
channels: null,
|
||||
description: null,
|
||||
error: String(err)
|
||||
};
|
||||
} finally {
|
||||
isQueryingDac = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset state when modal opens
|
||||
$effect(() => {
|
||||
if (isOpen) {
|
||||
@@ -81,6 +122,8 @@
|
||||
precheckDone = false;
|
||||
restartDone = false;
|
||||
showRollback = false;
|
||||
dacCapabilities = null;
|
||||
isQueryingDac = false;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -327,6 +370,71 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Query DAC Capabilities -->
|
||||
{#if dacValidation === 'valid'}
|
||||
<div class="query-section">
|
||||
<button
|
||||
class="query-btn"
|
||||
onclick={queryDacCapabilities}
|
||||
disabled={isQueryingDac}
|
||||
>
|
||||
{#if isQueryingDac}
|
||||
<Loader2 size={16} class="spin" />
|
||||
{$t('dacWizard.detectDac.query.querying')}
|
||||
{:else}
|
||||
<Search size={16} />
|
||||
{$t('dacWizard.detectDac.query.button')}
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
{#if dacCapabilities}
|
||||
<div class="capabilities-result">
|
||||
{#if dacCapabilities.description}
|
||||
<div class="cap-row">
|
||||
<span class="cap-label">{$t('dacWizard.detectDac.query.device')}:</span>
|
||||
<span class="cap-value">{dacCapabilities.description}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if dacCapabilities.sample_rates.length > 0}
|
||||
<div class="cap-row">
|
||||
<span class="cap-label">{$t('dacWizard.detectDac.query.sampleRates')}:</span>
|
||||
<span class="cap-value rates">
|
||||
{dacCapabilities.sample_rates.map(r => `${(r / 1000).toFixed(1)}kHz`).join(', ')}
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if dacCapabilities.formats.length > 0}
|
||||
<div class="cap-row">
|
||||
<span class="cap-label">{$t('dacWizard.detectDac.query.formats')}:</span>
|
||||
<span class="cap-value">{dacCapabilities.formats.join(', ')}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if dacCapabilities.channels}
|
||||
<div class="cap-row">
|
||||
<span class="cap-label">{$t('dacWizard.detectDac.query.channels')}:</span>
|
||||
<span class="cap-value">{dacCapabilities.channels}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if dacCapabilities.error}
|
||||
<WarningBanner
|
||||
variant="warning"
|
||||
body={dacCapabilities.error}
|
||||
/>
|
||||
{:else}
|
||||
<WarningBanner
|
||||
variant="info"
|
||||
body={$t('dacWizard.detectDac.query.disclaimer')}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{:else if currentStep === 'backup'}
|
||||
@@ -732,6 +840,79 @@
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
}
|
||||
|
||||
.query-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.query-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 10px 16px;
|
||||
background: var(--accent-primary);
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: opacity 150ms ease;
|
||||
}
|
||||
|
||||
.query-btn:hover:not(:disabled) {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.query-btn:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.query-btn :global(.spin) {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.capabilities-result {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.cap-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.cap-label {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.cap-value {
|
||||
font-size: 14px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.cap-value.rates {
|
||||
font-family: var(--font-mono, monospace);
|
||||
color: var(--color-success, #22c55e);
|
||||
}
|
||||
|
||||
.targeting-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -999,6 +999,15 @@
|
||||
"pciDac": "Internes Audio - Bit-Perfect abhängig von Hardware",
|
||||
"bluetoothDac": "Bluetooth-Gerät - Bit-Perfect nicht möglich",
|
||||
"virtualDac": "Virtuelles Gerät - Bit-Perfect nicht anwendbar"
|
||||
},
|
||||
"query": {
|
||||
"button": "DAC-Fähigkeiten abfragen",
|
||||
"querying": "Abfrage läuft...",
|
||||
"device": "Gerät",
|
||||
"sampleRates": "Abtastraten",
|
||||
"formats": "Formate",
|
||||
"channels": "Kanäle",
|
||||
"disclaimer": "Wenn diese Werte nicht mit den DAC-Spezifikationen übereinstimmen, prüfen Sie die Herstellerdokumentation."
|
||||
}
|
||||
},
|
||||
"backup": {
|
||||
|
||||
@@ -1058,6 +1058,15 @@
|
||||
"pciDac": "Internal audio - bit-perfect depends on hardware",
|
||||
"bluetoothDac": "Bluetooth device - bit-perfect not possible",
|
||||
"virtualDac": "Virtual device - bit-perfect not applicable"
|
||||
},
|
||||
"query": {
|
||||
"button": "Query DAC Capabilities",
|
||||
"querying": "Querying...",
|
||||
"device": "Device",
|
||||
"sampleRates": "Sample Rates",
|
||||
"formats": "Formats",
|
||||
"channels": "Channels",
|
||||
"disclaimer": "If these values don't match your DAC specs, check the manufacturer documentation."
|
||||
}
|
||||
},
|
||||
"backup": {
|
||||
|
||||
@@ -1058,6 +1058,15 @@
|
||||
"pciDac": "Audio interno - bit-perfect depende del hardware",
|
||||
"bluetoothDac": "Dispositivo Bluetooth - bit-perfect no es posible",
|
||||
"virtualDac": "Dispositivo virtual - bit-perfect no aplica"
|
||||
},
|
||||
"query": {
|
||||
"button": "Consultar Capacidades del DAC",
|
||||
"querying": "Consultando...",
|
||||
"device": "Dispositivo",
|
||||
"sampleRates": "Sample Rates",
|
||||
"formats": "Formatos",
|
||||
"channels": "Canales",
|
||||
"disclaimer": "Si estos valores no coinciden con las especificaciones de tu DAC, consulta la documentación del fabricante."
|
||||
}
|
||||
},
|
||||
"backup": {
|
||||
|
||||
@@ -999,6 +999,15 @@
|
||||
"pciDac": "Audio interne - bit-perfect dépend du matériel",
|
||||
"bluetoothDac": "Appareil Bluetooth - bit-perfect impossible",
|
||||
"virtualDac": "Appareil virtuel - bit-perfect non applicable"
|
||||
},
|
||||
"query": {
|
||||
"button": "Interroger les capacités du DAC",
|
||||
"querying": "Interrogation...",
|
||||
"device": "Appareil",
|
||||
"sampleRates": "Taux d'échantillonnage",
|
||||
"formats": "Formats",
|
||||
"channels": "Canaux",
|
||||
"disclaimer": "Si ces valeurs ne correspondent pas aux spécifications de votre DAC, consultez la documentation du fabricant."
|
||||
}
|
||||
},
|
||||
"backup": {
|
||||
|
||||
Reference in new issue
Block a user