feat(settings): surface graphics recommendation banner in the Graphics tab
The detection + decision matrix in autoconfig_graphics.rs already knew
the right combination for every (GPU vendor x display server x desktop)
case, but the only way to consume it was the qbz --autoconfig-graphics
CLI tool which most users never run. The recommendation sat invisible
while users hunted toggles by hand.
Lifts the same logic into the Settings UI as a persistent banner at
the top of the new Graphics tab:
- autoconfig_graphics: Environment and Recommendation structs become
public with Serialize, the three pieces (detect_environment,
compute_recommendation, write_recommendation) become pub so they
can be re-used. The existing apply_recommendation CLI helper now
delegates writes to the shared write_recommendation, keeping the
behaviour identical (and inverting the DMA-BUF mapping so that
rec.disable_dmabuf=false now writes force_dmabuf=true, matching
the 1.2.13 opt-in semantics).
- commands_v2/graphics_recommendation.rs (new): two thin Tauri
commands — v2_get_graphics_recommendation returns the environment
plus the computed recommendation so the frontend can compare it
to persisted settings; v2_apply_graphics_recommendation writes
the whole bundle in one call.
- Settings UI: the Graphics tab loads the recommendation on mount.
A $derived computes whether at least one recommended value
diverges from the four persisted settings (HW accel / force_x11 /
gsk_renderer / force_dmabuf with the inverted DMA-BUF axis).
When it does, a violet-accent banner appears above the profile
grid showing the detected environment, the rationale lines from
the matrix, and an Apply button. Persistent (no dismiss) — vanishes
the moment the persisted state matches the recommendation, no
second copy of state to keep in sync.
- i18n: 4 new keys per locale (title / detected / apply / applied)
across en/es/de/fr/pt. The rationale strings stay in English
because they're hardcoded in autoconfig_graphics.rs and reword
them per scenario would mean a structured i18n key per case —
not worth it for diagnostic copy that's read once and dismissed.
cc
This commit is contained in:
10 files changed
+314
-49
No files matched your search
@@ -389,6 +389,34 @@
|
||||
let gdkScale = $state('');
|
||||
let gdkDpiScale = $state('');
|
||||
let gskRenderer = $state('');
|
||||
|
||||
// Graphics recommendation surfaced in the Graphics tab. Populated on
|
||||
// mount via v2_get_graphics_recommendation (matrix lives in
|
||||
// autoconfig_graphics.rs). The banner fires only when at least one
|
||||
// recommended value differs from the currently persisted setting; the
|
||||
// "Apply" button writes the entire recommendation via
|
||||
// v2_apply_graphics_recommendation.
|
||||
type GraphicsRecommendation = {
|
||||
environment: {
|
||||
display_server: string;
|
||||
gpu_nvidia: boolean;
|
||||
gpu_amd: boolean;
|
||||
gpu_intel: boolean;
|
||||
gpu_name: string;
|
||||
desktop: string;
|
||||
is_vm: boolean;
|
||||
};
|
||||
recommendation: {
|
||||
hardware_acceleration: boolean;
|
||||
force_x11: boolean;
|
||||
gsk_renderer: string | null;
|
||||
disable_dmabuf: boolean;
|
||||
disable_blur_background: boolean;
|
||||
rationale: string[];
|
||||
};
|
||||
};
|
||||
let graphicsRecommendation = $state<GraphicsRecommendation | null>(null);
|
||||
|
||||
// Graphics startup status (for showing degraded mode warning)
|
||||
let graphicsUsingFallback = $state(false);
|
||||
let graphicsIsWayland = $state(false);
|
||||
@@ -1809,6 +1837,17 @@
|
||||
graphicsHwAccelEnabled = status.hardware_accel_enabled;
|
||||
}).catch(() => {});
|
||||
|
||||
// Linux-only: recommendation banner in the Graphics tab
|
||||
if (platform === 'linux') {
|
||||
invoke<GraphicsRecommendation>('v2_get_graphics_recommendation')
|
||||
.then((payload) => {
|
||||
graphicsRecommendation = payload;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.warn('[Settings] graphics recommendation unavailable:', err);
|
||||
});
|
||||
}
|
||||
|
||||
// Subscribe to offline state changes
|
||||
const unsubscribeOffline = subscribeOffline(() => {
|
||||
offlineStatus = getOfflineStatus();
|
||||
@@ -4064,6 +4103,53 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Whether the auto-detected recommendation diverges from any of the
|
||||
// four persisted graphics settings. The DMA-BUF axis is inverted:
|
||||
// recommendation `disable_dmabuf=true` maps to `force_dmabuf=false`
|
||||
// (1.2.13 opt-in semantics), so the two booleans differ iff
|
||||
// `disable_dmabuf === force_dmabuf` (same value means mismatch given
|
||||
// the inversion).
|
||||
const recommendationDiffers = $derived.by(() => {
|
||||
if (!graphicsRecommendation) return false;
|
||||
const rec = graphicsRecommendation.recommendation;
|
||||
if (rec.hardware_acceleration !== hardwareAcceleration) return true;
|
||||
if (rec.force_x11 !== forceX11) return true;
|
||||
if (rec.disable_dmabuf === forceDmabuf) return true;
|
||||
if ((rec.gsk_renderer ?? '') !== gskRenderer) return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
async function handleApplyGraphicsRecommendation() {
|
||||
try {
|
||||
const errors = await invoke<string[]>('v2_apply_graphics_recommendation');
|
||||
if (errors && errors.length > 0) {
|
||||
console.error('Failed to apply some graphics settings:', errors);
|
||||
showToast(errors.join('; '), 'error');
|
||||
return;
|
||||
}
|
||||
// Refresh local state from DB so the toggles snap to the new values
|
||||
const gs = await invoke<any>('v2_get_graphics_settings');
|
||||
forceX11 = gs.force_x11;
|
||||
gskRenderer = gs.gsk_renderer || '';
|
||||
hardwareAcceleration = gs.hardware_acceleration;
|
||||
const ds = await invoke<any>('v2_get_developer_settings');
|
||||
forceDmabuf = ds.force_dmabuf;
|
||||
showToast($t('settings.graphics.recommendation.applied'), 'success');
|
||||
} catch (err) {
|
||||
console.error('Failed to apply graphics recommendation:', err);
|
||||
showToast(String(err), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function buildEnvironmentSummary(env: GraphicsRecommendation['environment']): string {
|
||||
const parts: string[] = [];
|
||||
if (env.gpu_name) parts.push(env.gpu_name);
|
||||
if (env.display_server) parts.push(env.display_server);
|
||||
if (env.desktop && env.desktop !== 'Unknown') parts.push(env.desktop);
|
||||
if (env.is_vm) parts.push('VM');
|
||||
return parts.join(' / ');
|
||||
}
|
||||
|
||||
const GSK_RENDERER_KEYS = ['', 'gl', 'ngl', 'vulkan', 'cairo'] as const;
|
||||
|
||||
function getGskRendererOptions(): string[] {
|
||||
@@ -5217,6 +5303,29 @@
|
||||
<h3 class="section-title">{$t('settings.graphics.title')}</h3>
|
||||
<p class="section-note">{$t('settings.graphics.helpText')}</p>
|
||||
|
||||
{#if graphicsRecommendation && recommendationDiffers}
|
||||
<div class="recommendation-banner">
|
||||
<div class="recommendation-info">
|
||||
<span class="recommendation-title">{$t('settings.graphics.recommendation.title')}</span>
|
||||
<span class="recommendation-detected">
|
||||
{$t('settings.graphics.recommendation.detected')}: {buildEnvironmentSummary(graphicsRecommendation.environment)}
|
||||
</span>
|
||||
{#if graphicsRecommendation.recommendation.rationale.length > 0}
|
||||
<ul class="recommendation-rationale">
|
||||
{#each graphicsRecommendation.recommendation.rationale as line}
|
||||
<li>{line}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="recommendation-actions">
|
||||
<button class="primary-btn" onclick={handleApplyGraphicsRecommendation}>
|
||||
{$t('settings.graphics.recommendation.apply')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if graphicsUsingFallback}
|
||||
<div class="composition-warning fallback-warning">
|
||||
<TriangleAlert size={14} />
|
||||
@@ -7019,6 +7128,58 @@ flatpak override --user --filesystem=/home/USUARIO/Música com.blitzfc.qbz</pre>
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
/* Graphics recommendation banner. Shown at the top of the Graphics
|
||||
section when the autoconfig recommendation diverges from the
|
||||
persisted settings. Persistent (no dismiss) so the user knows the
|
||||
option is available; disappears the moment recommendation == state. */
|
||||
.recommendation-banner {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
padding: 14px 16px;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
background: var(--accent-primary-tint, rgba(124, 58, 237, 0.08));
|
||||
border: 1px solid var(--accent-primary, rgba(124, 58, 237, 0.3));
|
||||
}
|
||||
|
||||
.recommendation-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.recommendation-title {
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.recommendation-detected {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.recommendation-rationale {
|
||||
margin: 4px 0 0;
|
||||
padding-left: 18px;
|
||||
list-style: disc;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.recommendation-rationale li {
|
||||
margin: 1px 0;
|
||||
}
|
||||
|
||||
.recommendation-actions {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.fallback-desc {
|
||||
display: block;
|
||||
color: var(--text-secondary);
|
||||
|
||||
@@ -2821,6 +2821,12 @@
|
||||
"maxPerformanceLabel": "Maximale Leistung",
|
||||
"maxPerformanceDesc": "Software-Rendering (Cairo) mit deaktiviertem Blur. Maximale Kompatibilitaet, niedrigste GPU-Nutzung. Verwenden bei Ruckeln oder Abstuerzen.",
|
||||
"applied": "Profil angewendet: {profile}. QBZ neu starten, damit es wirksam wird."
|
||||
},
|
||||
"recommendation": {
|
||||
"title": "Empfohlen für dein System",
|
||||
"detected": "Erkannt",
|
||||
"apply": "Übernehmen",
|
||||
"applied": "Konfiguration übernommen. QBZ neu starten, damit es wirksam wird."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2827,6 +2827,12 @@
|
||||
"maxPerformanceLabel": "Max Performance",
|
||||
"maxPerformanceDesc": "Software rendering (Cairo) with blur disabled. Maximum compatibility, lowest GPU usage. Use if experiencing stutter or crashes.",
|
||||
"applied": "Applied profile: {profile}. Restart QBZ to take effect."
|
||||
},
|
||||
"recommendation": {
|
||||
"title": "Recommended for your system",
|
||||
"detected": "Detected",
|
||||
"apply": "Apply",
|
||||
"applied": "Configuration applied. Restart QBZ to take effect."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2827,6 +2827,12 @@
|
||||
"maxPerformanceLabel": "Rendimiento maximo",
|
||||
"maxPerformanceDesc": "Renderizado por software (Cairo) con blur desactivado. Maxima compatibilidad, menor uso de GPU. Usar si hay tartamudeo o crashes.",
|
||||
"applied": "Perfil aplicado: {profile}. Reinicia QBZ para que surta efecto."
|
||||
},
|
||||
"recommendation": {
|
||||
"title": "Recomendado para tu sistema",
|
||||
"detected": "Detectado",
|
||||
"apply": "Aplicar",
|
||||
"applied": "Configuracion aplicada. Reinicia QBZ para que surta efecto."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2821,6 +2821,12 @@
|
||||
"maxPerformanceLabel": "Performance maximale",
|
||||
"maxPerformanceDesc": "Rendu logiciel (Cairo) avec flou désactivé. Compatibilité maximale, utilisation GPU minimale. À utiliser en cas de saccades ou de plantages.",
|
||||
"applied": "Profil appliqué : {profile}. Redémarrez QBZ pour appliquer le changement."
|
||||
},
|
||||
"recommendation": {
|
||||
"title": "Recommandé pour votre système",
|
||||
"detected": "Détecté",
|
||||
"apply": "Appliquer",
|
||||
"applied": "Configuration appliquée. Redémarrez QBZ pour qu'elle prenne effet."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2821,6 +2821,12 @@
|
||||
"maxPerformanceLabel": "Desempenho Máximo",
|
||||
"maxPerformanceDesc": "Renderização por software (Cairo) com desfoque desativado. Máxima compatibilidade, menor uso de GPU. Use se tiver engasgos ou travamentos.",
|
||||
"applied": "Perfil aplicado: {profile}. Reinicie o QBZ para aplicar."
|
||||
},
|
||||
"recommendation": {
|
||||
"title": "Recomendado para o seu sistema",
|
||||
"detected": "Detectado",
|
||||
"apply": "Aplicar",
|
||||
"applied": "Configuracao aplicada. Reinicie o QBZ para que tenha efeito."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user