feat(window-settings): add TitlebarMode enum and titlebar_mode column
Adds a 4-value mode enum (Qbz | System | Plasma | Hidden) to the existing WindowSettings store, with column migration that infers mode from the legacy use_system_titlebar flag. Setter is write-through so the legacy column stays consistent for any remaining readers. cc
This commit is contained in:
1 file changed
+126
-2
@@ -9,9 +9,44 @@ use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// Titlebar rendering mode. Controls which decoration source is active and
|
||||
/// how the custom titlebar component renders (or whether it renders at all).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum TitlebarMode {
|
||||
/// Default — full custom titlebar with drag, resize, dblclick, controls.
|
||||
Qbz,
|
||||
/// OS-native chrome (CSD on Wayland, KWin SSD on X11, NSWindow on macOS).
|
||||
System,
|
||||
/// KDE-only — KWin SSD via Xwayland + stripped custom strip below for search+nav.
|
||||
Plasma,
|
||||
/// No titlebar — for tiling WMs (i3/Hyprland/sway/niri).
|
||||
Hidden,
|
||||
}
|
||||
|
||||
impl TitlebarMode {
|
||||
/// Whether this mode requests OS decorations (decorations=true at window creation).
|
||||
pub fn wants_decorations(self) -> bool {
|
||||
matches!(self, TitlebarMode::System | TitlebarMode::Plasma)
|
||||
}
|
||||
|
||||
/// Whether this mode requires GDK_BACKEND=x11 to coerce SSD on Wayland.
|
||||
pub fn wants_xwayland(self) -> bool {
|
||||
matches!(self, TitlebarMode::Plasma)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TitlebarMode {
|
||||
fn default() -> Self {
|
||||
TitlebarMode::Qbz
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct WindowSettings {
|
||||
/// Use OS native window decorations instead of custom title bar
|
||||
/// Use OS native window decorations (legacy — superseded by `titlebar_mode`).
|
||||
/// Kept as a write-through shadow of `titlebar_mode in {System, Plasma}` for
|
||||
/// any consumers that still read it; will be removed in a follow-up.
|
||||
pub use_system_titlebar: bool,
|
||||
/// Last saved window width (logical pixels, non-maximized)
|
||||
pub window_width: f64,
|
||||
@@ -23,6 +58,8 @@ pub struct WindowSettings {
|
||||
/// Implies building the window transparent on Linux so the corners
|
||||
/// can be seen through to the desktop. Requires restart to take effect.
|
||||
pub match_system_window_chrome: bool,
|
||||
/// Authoritative titlebar mode (replaces `use_system_titlebar`).
|
||||
pub titlebar_mode: TitlebarMode,
|
||||
}
|
||||
|
||||
impl Default for WindowSettings {
|
||||
@@ -33,6 +70,7 @@ impl Default for WindowSettings {
|
||||
window_height: 800.0,
|
||||
is_maximized: false,
|
||||
match_system_window_chrome: false,
|
||||
titlebar_mode: TitlebarMode::Qbz,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,6 +124,26 @@ impl WindowSettingsStore {
|
||||
[],
|
||||
);
|
||||
|
||||
// Add titlebar_mode column. Defaults to NULL so we can detect
|
||||
// pre-existing rows and migrate from use_system_titlebar.
|
||||
let _ = conn.execute(
|
||||
"ALTER TABLE window_settings ADD COLUMN titlebar_mode TEXT",
|
||||
[],
|
||||
);
|
||||
|
||||
// One-time migration: if titlebar_mode is NULL but use_system_titlebar
|
||||
// exists, derive mode. This runs at every open but only does work on
|
||||
// rows where titlebar_mode is still NULL (idempotent).
|
||||
let _ = conn.execute(
|
||||
"UPDATE window_settings
|
||||
SET titlebar_mode = CASE
|
||||
WHEN use_system_titlebar = 1 THEN 'system'
|
||||
ELSE 'qbz'
|
||||
END
|
||||
WHERE titlebar_mode IS NULL AND id = 1",
|
||||
[],
|
||||
);
|
||||
|
||||
info!("[WindowSettings] Database initialized");
|
||||
|
||||
Ok(Self { conn })
|
||||
@@ -102,11 +160,32 @@ impl WindowSettingsStore {
|
||||
Self::open_at(base_dir, "window_settings.db")
|
||||
}
|
||||
|
||||
/// 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("window_settings.db");
|
||||
|
||||
if !db_path.exists() {
|
||||
return Err("Window 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 window settings database (readonly): {}", e))?;
|
||||
|
||||
Ok(Self { conn })
|
||||
}
|
||||
|
||||
pub fn get_settings(&self) -> Result<WindowSettings, String> {
|
||||
self.conn
|
||||
.query_row(
|
||||
"SELECT use_system_titlebar, window_width, window_height, is_maximized,
|
||||
match_system_window_chrome
|
||||
match_system_window_chrome, titlebar_mode
|
||||
FROM window_settings WHERE id = 1",
|
||||
[],
|
||||
|row| {
|
||||
@@ -115,6 +194,13 @@ impl WindowSettingsStore {
|
||||
let window_height: f64 = row.get(2)?;
|
||||
let is_maximized: i32 = row.get(3)?;
|
||||
let match_chrome: i32 = row.get(4)?;
|
||||
let mode_str: Option<String> = row.get(5)?;
|
||||
let titlebar_mode = match mode_str.as_deref() {
|
||||
Some("system") => TitlebarMode::System,
|
||||
Some("plasma") => TitlebarMode::Plasma,
|
||||
Some("hidden") => TitlebarMode::Hidden,
|
||||
_ => TitlebarMode::Qbz,
|
||||
};
|
||||
let defaults = WindowSettings::default();
|
||||
let (w, h) = if is_valid_window_size(window_width, window_height) {
|
||||
(window_width, window_height)
|
||||
@@ -132,6 +218,7 @@ impl WindowSettingsStore {
|
||||
window_height: h,
|
||||
is_maximized: is_maximized != 0,
|
||||
match_system_window_chrome: match_chrome != 0,
|
||||
titlebar_mode,
|
||||
})
|
||||
},
|
||||
)
|
||||
@@ -158,6 +245,26 @@ impl WindowSettingsStore {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_titlebar_mode(&self, mode: TitlebarMode) -> Result<(), String> {
|
||||
let mode_str = match mode {
|
||||
TitlebarMode::Qbz => "qbz",
|
||||
TitlebarMode::System => "system",
|
||||
TitlebarMode::Plasma => "plasma",
|
||||
TitlebarMode::Hidden => "hidden",
|
||||
};
|
||||
let use_system = mode.wants_decorations() as i64;
|
||||
self.conn
|
||||
.execute(
|
||||
"UPDATE window_settings
|
||||
SET titlebar_mode = ?1, use_system_titlebar = ?2
|
||||
WHERE id = 1",
|
||||
params![mode_str, use_system],
|
||||
)
|
||||
.map_err(|e| format!("Failed to set titlebar_mode: {}", e))?;
|
||||
info!("Window titlebar_mode set to {}", mode_str);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Save the non-maximized window dimensions (called on resize while not maximized).
|
||||
pub fn set_window_size(&self, width: f64, height: f64) -> Result<(), String> {
|
||||
if !is_valid_window_size(width, height) {
|
||||
@@ -262,6 +369,23 @@ impl WindowSettingsState {
|
||||
.ok_or("Window settings store not initialized")?;
|
||||
store.set_match_system_window_chrome(value)
|
||||
}
|
||||
|
||||
pub fn set_titlebar_mode(&self, mode: TitlebarMode) -> Result<(), String> {
|
||||
let store = self.store.lock().map_err(|e| format!("lock poisoned: {}", e))?;
|
||||
store
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Window settings store not initialized".to_string())?
|
||||
.set_titlebar_mode(mode)
|
||||
}
|
||||
|
||||
pub fn get_titlebar_mode(&self) -> Result<TitlebarMode, String> {
|
||||
let store = self.store.lock().map_err(|e| format!("lock poisoned: {}", e))?;
|
||||
let settings = store
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Window settings store not initialized".to_string())?
|
||||
.get_settings()?;
|
||||
Ok(settings.titlebar_mode)
|
||||
}
|
||||
}
|
||||
|
||||
/// Check that window dimensions are within a sane range.
|
||||
|
||||
Reference in new issue
Block a user