refactor(tauri): use qbz-app graphics settings

cx
This commit is contained in:
vicrodh committed 2026-05-15 18:53:24 -06:00
1 parent bcdbc69913
commit 441bcc5f0e
1 file changed
+5 -236
+5 -236
View File
@@ -1,19 +1,14 @@
//! Graphics settings persistence
//! Tauri graphics settings commands and startup status.
//!
//! Stores GPU/rendering preferences that take effect before WebView initialization.
//! These settings are device-level (not per-user) and persist across sessions.
//!
//! - hardware_acceleration: GPU rendering toggle (default: on). Read at startup
//! as the default value; env var QBZ_HARDWARE_ACCEL=0|1 overrides.
//! - force_x11: force X11/XWayland backend on Wayland sessions (default: off)
//! Env var QBZ_FORCE_X11=1|0 always overrides the stored value.
//! - gdk_scale / gdk_dpi_scale: display scaling overrides for XWayland
//! Portable graphics settings persistence lives in `qbz-app`. This module keeps
//! Tauri command wrappers and process-local startup status only.
use rusqlite::{params, Connection};
use serde::{Deserialize, Serialize};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
pub use qbz_app::settings::graphics::{GraphicsSettings, GraphicsSettingsStore};
// --- Global startup state (set once in main.rs, read-only thereafter) ---
static GRAPHICS_USING_FALLBACK: AtomicBool = AtomicBool::new(false);
static GRAPHICS_IS_WAYLAND: AtomicBool = AtomicBool::new(false);
@@ -50,232 +45,6 @@ pub fn is_using_graphics_fallback() -> bool {
GRAPHICS_USING_FALLBACK.load(Ordering::SeqCst)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphicsSettings {
/// GPU rendering toggle. Read at startup as default; env var QBZ_HARDWARE_ACCEL overrides.
pub hardware_acceleration: bool,
/// Force X11 (XWayland) backend on Wayland sessions (requires restart)
pub force_x11: bool,
/// GDK_SCALE override for XWayland (None = auto). Integer values: "1", "2"
pub gdk_scale: Option<String>,
/// GDK_DPI_SCALE override for XWayland (None = auto). Float values: "0.5", "1", "1.5"
pub gdk_dpi_scale: Option<String>,
/// GSK_RENDERER override (None = auto). Values: "gl", "ngl", "vulkan", "cairo"
pub gsk_renderer: Option<String>,
/// Rendering GPU selection. Values:
/// "auto" — let the system pick (default).
/// "integrated" — pin to the iGPU (Intel/AMD).
/// "discrete" — pin to the dGPU (NVIDIA, or AMD/Intel discrete).
/// "software" — software rendering via llvmpipe.
/// "<pci-slot>" — pin to a specific detected GPU (e.g. "0000:01:00.0").
/// main.rs applies the choice via env vars (EGL vendor pin, DRI_PRIME,
/// NV_PRIME_RENDER_OFFLOAD) before WebKit init. Requires restart to
/// take effect.
pub preferred_gpu: String,
/// Opt-in NVIDIA Wayland compatibility mode. When ON:
/// - Sets `__NV_DISABLE_EXPLICIT_SYNC=1` to bypass the buggy
/// implicit-sync paths in libnvidia-egl-gbm that produce
/// "Failed to create GBM buffer of size NxN: Invalid argument"
/// under WebKit on NVIDIA + Wayland (driver 550+).
/// - Lifts the defensive compositing-mode shutdown that QBZ
/// applies to NVIDIA-only Wayland sessions — the user is
/// signaling that the real fix is now in place, so the
/// safety net is no longer needed.
/// Off by default. Requires a driver+system that ships
/// libnvidia-egl-gbm to be useful.
pub nvidia_compat_mode: bool,
}
impl Default for GraphicsSettings {
fn default() -> Self {
Self {
hardware_acceleration: true,
force_x11: false,
gdk_scale: None,
gdk_dpi_scale: None,
gsk_renderer: None,
preferred_gpu: "auto".to_string(),
nvidia_compat_mode: false,
}
}
}
pub struct GraphicsSettingsStore {
conn: Connection,
}
impl GraphicsSettingsStore {
/// Lightweight read-only open for startup (before Tauri state).
/// Opens existing DB without creating tables/running migrations.
pub fn new_readonly() -> Result<Self, String> {
let db_path = dirs::data_dir()
.ok_or("Could not determine data directory")?
.join("qbz")
.join("graphics_settings.db");
if !db_path.exists() {
return Err("Graphics settings DB does not exist yet".to_string());
}
let conn = Connection::open_with_flags(
&db_path,
rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY | rusqlite::OpenFlags::SQLITE_OPEN_NO_MUTEX,
)
.map_err(|e| format!("Failed to open graphics settings database (readonly): {}", e))?;
Ok(Self { conn })
}
pub fn new() -> Result<Self, String> {
let data_dir = dirs::data_dir()
.ok_or("Could not determine data directory")?
.join("qbz");
std::fs::create_dir_all(&data_dir)
.map_err(|e| format!("Failed to create data directory: {}", e))?;
let db_path = data_dir.join("graphics_settings.db");
let conn = Connection::open(&db_path)
.map_err(|e| format!("Failed to open graphics settings database: {}", e))?;
conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL;")
.map_err(|e| format!("Failed to enable WAL for graphics settings database: {}", e))?;
// Default hardware_acceleration = 1. Earlier QBZ versions defaulted
// to 0 "for maximum compatibility", but WebKitGTK without GPU
// compositing is severely degraded (multi-second component mounts
// on 4K / tiled windows; observed 2026-05-12 perf session). The
// mitigation logic in main.rs already auto-detects NVIDIA-only
// Wayland configs and disables compositing for them; for every
// other config the GPU path is the better default. Users who
// explicitly toggle off in Settings keep their preference (the
// INSERT OR IGNORE below only seeds a row that doesn't exist).
conn.execute_batch(
"CREATE TABLE IF NOT EXISTS graphics_settings (
id INTEGER PRIMARY KEY CHECK (id = 1),
hardware_acceleration INTEGER NOT NULL DEFAULT 1
);
INSERT OR IGNORE INTO graphics_settings (id, hardware_acceleration) VALUES (1, 1);",
)
.map_err(|e| format!("Failed to create graphics settings table: {}", e))?;
// Migrations: add columns (no-op if already present)
let _ = conn.execute_batch(
"ALTER TABLE graphics_settings ADD COLUMN force_x11 INTEGER NOT NULL DEFAULT 0;",
);
let _ = conn.execute_batch("ALTER TABLE graphics_settings ADD COLUMN gdk_scale TEXT;");
let _ = conn.execute_batch("ALTER TABLE graphics_settings ADD COLUMN gdk_dpi_scale TEXT;");
let _ = conn.execute_batch("ALTER TABLE graphics_settings ADD COLUMN gsk_renderer TEXT;");
// 1.2.13: rendering GPU selection. Default 'auto' so existing
// installs keep their current behaviour after migration. Stored
// as TEXT to accept both the well-known sentinels (auto /
// integrated / discrete / software) and specific PCI slot ids.
let _ = conn.execute_batch(
"ALTER TABLE graphics_settings ADD COLUMN preferred_gpu TEXT NOT NULL DEFAULT 'auto';",
);
// 1.2.13: NVIDIA Wayland compatibility opt-in. Default OFF so
// existing installs don't pick up a behaviour change on migration
// — users explicitly enable it after installing libnvidia-egl-gbm.
let _ = conn.execute_batch(
"ALTER TABLE graphics_settings ADD COLUMN nvidia_compat_mode INTEGER NOT NULL DEFAULT 0;",
);
Ok(Self { conn })
}
pub fn get_settings(&self) -> Result<GraphicsSettings, String> {
self.conn
.query_row(
"SELECT hardware_acceleration, force_x11, gdk_scale, gdk_dpi_scale, gsk_renderer, preferred_gpu, nvidia_compat_mode FROM graphics_settings WHERE id = 1",
[],
|row| {
Ok(GraphicsSettings {
hardware_acceleration: row.get::<_, i64>(0)? != 0,
force_x11: row.get::<_, i64>(1)? != 0,
gdk_scale: row.get::<_, Option<String>>(2)?,
gdk_dpi_scale: row.get::<_, Option<String>>(3)?,
gsk_renderer: row.get::<_, Option<String>>(4)?,
preferred_gpu: row
.get::<_, Option<String>>(5)?
.unwrap_or_else(|| "auto".to_string()),
nvidia_compat_mode: row.get::<_, i64>(6).unwrap_or(0) != 0,
})
},
)
.map_err(|e| format!("Failed to get graphics settings: {}", e))
}
pub fn set_hardware_acceleration(&self, enabled: bool) -> Result<(), String> {
self.conn
.execute(
"UPDATE graphics_settings SET hardware_acceleration = ?1 WHERE id = 1",
params![enabled as i64],
)
.map_err(|e| format!("Failed to set hardware_acceleration: {}", e))?;
Ok(())
}
pub fn set_force_x11(&self, enabled: bool) -> Result<(), String> {
self.conn
.execute(
"UPDATE graphics_settings SET force_x11 = ?1 WHERE id = 1",
params![enabled as i64],
)
.map_err(|e| format!("Failed to set force_x11: {}", e))?;
Ok(())
}
pub fn set_gdk_scale(&self, value: Option<String>) -> Result<(), String> {
self.conn
.execute(
"UPDATE graphics_settings SET gdk_scale = ?1 WHERE id = 1",
params![value],
)
.map_err(|e| format!("Failed to set gdk_scale: {}", e))?;
Ok(())
}
pub fn set_gdk_dpi_scale(&self, value: Option<String>) -> Result<(), String> {
self.conn
.execute(
"UPDATE graphics_settings SET gdk_dpi_scale = ?1 WHERE id = 1",
params![value],
)
.map_err(|e| format!("Failed to set gdk_dpi_scale: {}", e))?;
Ok(())
}
pub fn set_gsk_renderer(&self, value: Option<String>) -> Result<(), String> {
self.conn
.execute(
"UPDATE graphics_settings SET gsk_renderer = ?1 WHERE id = 1",
params![value],
)
.map_err(|e| format!("Failed to set gsk_renderer: {}", e))?;
Ok(())
}
pub fn set_preferred_gpu(&self, value: &str) -> Result<(), String> {
self.conn
.execute(
"UPDATE graphics_settings SET preferred_gpu = ?1 WHERE id = 1",
params![value],
)
.map_err(|e| format!("Failed to set preferred_gpu: {}", e))?;
Ok(())
}
pub fn set_nvidia_compat_mode(&self, enabled: bool) -> Result<(), String> {
self.conn
.execute(
"UPDATE graphics_settings SET nvidia_compat_mode = ?1 WHERE id = 1",
params![enabled as i64],
)
.map_err(|e| format!("Failed to set nvidia_compat_mode: {}", e))?;
Ok(())
}
}
/// Thread-safe wrapper for Tauri state management
pub struct GraphicsSettingsState {
pub store: Arc<Mutex<Option<GraphicsSettingsStore>>>,