refactor(tauri): use qbz-app ui preference stores
cx
This commit is contained in:
2 files changed
+56
-669
No files matched your search
@@ -1,364 +1,12 @@
|
||||
//! Playback preferences
|
||||
//!
|
||||
//! Stores user preferences for playback behavior (autoplay mode, etc.)
|
||||
//! Tauri commands for playback and playback-adjacent UI preferences.
|
||||
|
||||
use log::info;
|
||||
use rusqlite::{params, Connection};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum AutoplayMode {
|
||||
/// Continue playing within the source (album, playlist, etc.)
|
||||
#[serde(rename = "continue")]
|
||||
ContinueWithinSource,
|
||||
/// Play only the selected track, then stop
|
||||
#[serde(rename = "track_only")]
|
||||
PlayTrackOnly,
|
||||
/// Create infinite radio when queue ends (based on recent tracks)
|
||||
#[serde(rename = "infinite")]
|
||||
InfiniteRadio,
|
||||
}
|
||||
|
||||
impl Default for AutoplayMode {
|
||||
fn default() -> Self {
|
||||
Self::ContinueWithinSource
|
||||
}
|
||||
}
|
||||
|
||||
impl AutoplayMode {
|
||||
fn to_db_value(&self) -> &'static str {
|
||||
match self {
|
||||
AutoplayMode::ContinueWithinSource => "continue",
|
||||
AutoplayMode::PlayTrackOnly => "track_only",
|
||||
AutoplayMode::InfiniteRadio => "infinite",
|
||||
}
|
||||
}
|
||||
|
||||
fn from_db_value(s: &str) -> Self {
|
||||
match s {
|
||||
"track_only" => AutoplayMode::PlayTrackOnly,
|
||||
"infinite" => AutoplayMode::InfiniteRadio,
|
||||
_ => AutoplayMode::ContinueWithinSource,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PlaybackPreferences {
|
||||
pub autoplay_mode: AutoplayMode,
|
||||
pub show_context_icon: bool,
|
||||
pub persist_session: bool,
|
||||
/// Sub-preference of `persist_session`. When true, restoring a
|
||||
/// session also seeks to `current_position_secs` of the saved
|
||||
/// track. When false (default), the saved track is shown paused at
|
||||
/// 0:00 and the user starts the next listen fresh — the common
|
||||
/// preference for "open app the next morning to a clean slate"
|
||||
/// (issue #317).
|
||||
pub resume_playback_position: bool,
|
||||
}
|
||||
|
||||
impl Default for PlaybackPreferences {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
autoplay_mode: AutoplayMode::ContinueWithinSource,
|
||||
show_context_icon: false,
|
||||
persist_session: false,
|
||||
resume_playback_position: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PlaybackPreferencesStore {
|
||||
conn: Connection,
|
||||
}
|
||||
|
||||
impl PlaybackPreferencesStore {
|
||||
fn open_at(dir: &Path, db_name: &str) -> Result<Self, String> {
|
||||
std::fs::create_dir_all(dir)
|
||||
.map_err(|e| format!("Failed to create data directory: {}", e))?;
|
||||
|
||||
let db_path = dir.join(db_name);
|
||||
let conn = Connection::open(&db_path)
|
||||
.map_err(|e| format!("Failed to open playback preferences database: {}", e))?;
|
||||
|
||||
conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL;")
|
||||
.map_err(|e| {
|
||||
format!(
|
||||
"Failed to enable WAL for playback preferences database: {}",
|
||||
e
|
||||
)
|
||||
})?;
|
||||
|
||||
// Step 1: Create table with old schema (for new installs) or do nothing if exists
|
||||
conn.execute_batch(
|
||||
"CREATE TABLE IF NOT EXISTS playback_preferences (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
autoplay_mode TEXT NOT NULL DEFAULT 'continue'
|
||||
);",
|
||||
)
|
||||
.map_err(|e| format!("Failed to create playback preferences table: {}", e))?;
|
||||
|
||||
// Step 2: Check if show_context_icon column exists
|
||||
let column_exists: bool = conn
|
||||
.query_row(
|
||||
"SELECT COUNT(*) FROM pragma_table_info('playback_preferences') WHERE name='show_context_icon'",
|
||||
[],
|
||||
|row| {
|
||||
let count: i32 = row.get(0)?;
|
||||
Ok(count > 0)
|
||||
}
|
||||
)
|
||||
.unwrap_or(false);
|
||||
|
||||
info!(
|
||||
"[PlaybackPrefs] Column show_context_icon exists: {}",
|
||||
column_exists
|
||||
);
|
||||
|
||||
// Step 3: Add column if it doesn't exist (migration for existing users)
|
||||
if !column_exists {
|
||||
info!("[PlaybackPrefs] Migrating: adding show_context_icon column");
|
||||
conn.execute(
|
||||
"ALTER TABLE playback_preferences ADD COLUMN show_context_icon INTEGER NOT NULL DEFAULT 0",
|
||||
[]
|
||||
).map_err(|e| format!("Failed to add show_context_icon column: {}", e))?;
|
||||
info!("[PlaybackPrefs] Migration successful");
|
||||
}
|
||||
|
||||
// Step 3b: Check if persist_session column exists
|
||||
let persist_session_exists: bool = conn
|
||||
.query_row(
|
||||
"SELECT COUNT(*) FROM pragma_table_info('playback_preferences') WHERE name='persist_session'",
|
||||
[],
|
||||
|row| {
|
||||
let count: i32 = row.get(0)?;
|
||||
Ok(count > 0)
|
||||
}
|
||||
)
|
||||
.unwrap_or(false);
|
||||
|
||||
if !persist_session_exists {
|
||||
info!("[PlaybackPrefs] Migrating: adding persist_session column");
|
||||
conn.execute(
|
||||
"ALTER TABLE playback_preferences ADD COLUMN persist_session INTEGER NOT NULL DEFAULT 0",
|
||||
[]
|
||||
).map_err(|e| format!("Failed to add persist_session column: {}", e))?;
|
||||
info!("[PlaybackPrefs] persist_session migration successful");
|
||||
}
|
||||
|
||||
// Step 3c: Check if resume_playback_position column exists
|
||||
let resume_position_exists: bool = conn
|
||||
.query_row(
|
||||
"SELECT COUNT(*) FROM pragma_table_info('playback_preferences') WHERE name='resume_playback_position'",
|
||||
[],
|
||||
|row| {
|
||||
let count: i32 = row.get(0)?;
|
||||
Ok(count > 0)
|
||||
}
|
||||
)
|
||||
.unwrap_or(false);
|
||||
|
||||
if !resume_position_exists {
|
||||
info!("[PlaybackPrefs] Migrating: adding resume_playback_position column");
|
||||
conn.execute(
|
||||
"ALTER TABLE playback_preferences ADD COLUMN resume_playback_position INTEGER NOT NULL DEFAULT 0",
|
||||
[]
|
||||
).map_err(|e| format!("Failed to add resume_playback_position column: {}", e))?;
|
||||
info!("[PlaybackPrefs] resume_playback_position migration successful");
|
||||
}
|
||||
|
||||
// Step 4: Insert default row if it doesn't exist
|
||||
conn.execute(
|
||||
"INSERT OR IGNORE INTO playback_preferences (id, autoplay_mode, show_context_icon, persist_session, resume_playback_position)
|
||||
VALUES (1, 'continue', 0, 0, 0)",
|
||||
[],
|
||||
)
|
||||
.map_err(|e| format!("Failed to insert default preferences: {}", 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");
|
||||
Self::open_at(&data_dir, "playback_preferences.db")
|
||||
}
|
||||
|
||||
pub fn new_at(base_dir: &Path) -> Result<Self, String> {
|
||||
Self::open_at(base_dir, "playback_preferences.db")
|
||||
}
|
||||
|
||||
pub fn get_preferences(&self) -> Result<PlaybackPreferences, String> {
|
||||
self.conn
|
||||
.query_row(
|
||||
"SELECT autoplay_mode, show_context_icon, persist_session, resume_playback_position FROM playback_preferences WHERE id = 1",
|
||||
[],
|
||||
|row| {
|
||||
let autoplay_str: String = row.get(0)?;
|
||||
let show_icon: i32 = row.get(1)?;
|
||||
let persist: i32 = row.get(2)?;
|
||||
let resume_pos: i32 = row.get(3)?;
|
||||
Ok(PlaybackPreferences {
|
||||
autoplay_mode: AutoplayMode::from_db_value(&autoplay_str),
|
||||
show_context_icon: show_icon != 0,
|
||||
persist_session: persist != 0,
|
||||
resume_playback_position: resume_pos != 0,
|
||||
})
|
||||
},
|
||||
)
|
||||
.map_err(|e| format!("Failed to get playback preferences: {}", e))
|
||||
}
|
||||
|
||||
pub fn set_autoplay_mode(&self, mode: AutoplayMode) -> Result<(), String> {
|
||||
self.conn
|
||||
.execute(
|
||||
"UPDATE playback_preferences SET autoplay_mode = ?1 WHERE id = 1",
|
||||
params![mode.to_db_value()],
|
||||
)
|
||||
.map_err(|e| format!("Failed to set autoplay mode: {}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_show_context_icon(&self, show: bool) -> Result<(), String> {
|
||||
self.conn
|
||||
.execute(
|
||||
"UPDATE playback_preferences SET show_context_icon = ?1 WHERE id = 1",
|
||||
params![if show { 1 } else { 0 }],
|
||||
)
|
||||
.map_err(|e| format!("Failed to set show context icon: {}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_persist_session(&self, persist: bool) -> Result<(), String> {
|
||||
self.conn
|
||||
.execute(
|
||||
"UPDATE playback_preferences SET persist_session = ?1 WHERE id = 1",
|
||||
params![if persist { 1 } else { 0 }],
|
||||
)
|
||||
.map_err(|e| format!("Failed to set persist session: {}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_resume_playback_position(&self, resume: bool) -> Result<(), String> {
|
||||
self.conn
|
||||
.execute(
|
||||
"UPDATE playback_preferences SET resume_playback_position = ?1 WHERE id = 1",
|
||||
params![if resume { 1 } else { 0 }],
|
||||
)
|
||||
.map_err(|e| format!("Failed to set resume playback position: {}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reset all playback preferences to their default values
|
||||
pub fn reset_all(&self) -> Result<PlaybackPreferences, String> {
|
||||
let defaults = PlaybackPreferences::default();
|
||||
self.conn
|
||||
.execute(
|
||||
"UPDATE playback_preferences SET autoplay_mode = ?1, show_context_icon = ?2, persist_session = ?3, resume_playback_position = ?4 WHERE id = 1",
|
||||
params![
|
||||
defaults.autoplay_mode.to_db_value(),
|
||||
if defaults.show_context_icon { 1 } else { 0 },
|
||||
if defaults.persist_session { 1 } else { 0 },
|
||||
if defaults.resume_playback_position { 1 } else { 0 },
|
||||
],
|
||||
)
|
||||
.map_err(|e| format!("Failed to reset playback preferences: {}", e))?;
|
||||
Ok(defaults)
|
||||
}
|
||||
}
|
||||
|
||||
/// Global state wrapper for thread-safe access
|
||||
pub struct PlaybackPreferencesState {
|
||||
pub store: Arc<Mutex<Option<PlaybackPreferencesStore>>>,
|
||||
}
|
||||
|
||||
impl PlaybackPreferencesState {
|
||||
pub fn new() -> Result<Self, String> {
|
||||
let store = PlaybackPreferencesStore::new()?;
|
||||
Ok(Self {
|
||||
store: Arc::new(Mutex::new(Some(store))),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_empty() -> Self {
|
||||
Self {
|
||||
store: Arc::new(Mutex::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_at(&self, base_dir: &Path) -> Result<(), String> {
|
||||
let new_store = PlaybackPreferencesStore::new_at(base_dir)?;
|
||||
let mut guard = self
|
||||
.store
|
||||
.lock()
|
||||
.map_err(|_| "Failed to lock playback preferences store".to_string())?;
|
||||
*guard = Some(new_store);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn teardown(&self) -> Result<(), String> {
|
||||
let mut guard = self
|
||||
.store
|
||||
.lock()
|
||||
.map_err(|_| "Failed to lock playback preferences store".to_string())?;
|
||||
*guard = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_preferences(&self) -> Result<PlaybackPreferences, String> {
|
||||
let guard = self
|
||||
.store
|
||||
.lock()
|
||||
.map_err(|_| "Failed to lock playback preferences store".to_string())?;
|
||||
let store = guard.as_ref().ok_or("No active session - please log in")?;
|
||||
store.get_preferences()
|
||||
}
|
||||
|
||||
pub fn set_autoplay_mode(&self, mode: AutoplayMode) -> Result<(), String> {
|
||||
let guard = self
|
||||
.store
|
||||
.lock()
|
||||
.map_err(|_| "Failed to lock playback preferences store".to_string())?;
|
||||
let store = guard.as_ref().ok_or("No active session - please log in")?;
|
||||
store.set_autoplay_mode(mode)
|
||||
}
|
||||
|
||||
pub fn set_show_context_icon(&self, show: bool) -> Result<(), String> {
|
||||
let guard = self
|
||||
.store
|
||||
.lock()
|
||||
.map_err(|_| "Failed to lock playback preferences store".to_string())?;
|
||||
let store = guard.as_ref().ok_or("No active session - please log in")?;
|
||||
store.set_show_context_icon(show)
|
||||
}
|
||||
|
||||
pub fn set_persist_session(&self, persist: bool) -> Result<(), String> {
|
||||
let guard = self
|
||||
.store
|
||||
.lock()
|
||||
.map_err(|_| "Failed to lock playback preferences store".to_string())?;
|
||||
let store = guard.as_ref().ok_or("No active session - please log in")?;
|
||||
store.set_persist_session(persist)
|
||||
}
|
||||
|
||||
pub fn set_resume_playback_position(&self, resume: bool) -> Result<(), String> {
|
||||
let guard = self
|
||||
.store
|
||||
.lock()
|
||||
.map_err(|_| "Failed to lock playback preferences store".to_string())?;
|
||||
let store = guard.as_ref().ok_or("No active session - please log in")?;
|
||||
store.set_resume_playback_position(resume)
|
||||
}
|
||||
}
|
||||
|
||||
// Tauri commands
|
||||
pub use qbz_app::settings::playback::{
|
||||
AutoplayMode, PlaybackPreferences, PlaybackPreferencesState, PlaybackPreferencesStore,
|
||||
};
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_playback_preferences(
|
||||
state: tauri::State<PlaybackPreferencesState>,
|
||||
state: tauri::State<'_, PlaybackPreferencesState>,
|
||||
) -> Result<PlaybackPreferences, String> {
|
||||
state.get_preferences()
|
||||
}
|
||||
@@ -366,7 +14,7 @@ pub fn get_playback_preferences(
|
||||
#[tauri::command]
|
||||
pub fn set_autoplay_mode(
|
||||
mode: String,
|
||||
state: tauri::State<PlaybackPreferencesState>,
|
||||
state: tauri::State<'_, PlaybackPreferencesState>,
|
||||
) -> Result<(), String> {
|
||||
let autoplay_mode = match mode.as_str() {
|
||||
"continue" => AutoplayMode::ContinueWithinSource,
|
||||
@@ -380,7 +28,7 @@ pub fn set_autoplay_mode(
|
||||
#[tauri::command]
|
||||
pub fn set_show_context_icon(
|
||||
show: bool,
|
||||
state: tauri::State<PlaybackPreferencesState>,
|
||||
state: tauri::State<'_, PlaybackPreferencesState>,
|
||||
) -> Result<(), String> {
|
||||
state.set_show_context_icon(show)
|
||||
}
|
||||
@@ -388,7 +36,7 @@ pub fn set_show_context_icon(
|
||||
#[tauri::command]
|
||||
pub fn set_persist_session(
|
||||
persist: bool,
|
||||
state: tauri::State<PlaybackPreferencesState>,
|
||||
state: tauri::State<'_, PlaybackPreferencesState>,
|
||||
) -> Result<(), String> {
|
||||
state.set_persist_session(persist)
|
||||
}
|
||||
@@ -396,7 +44,7 @@ pub fn set_persist_session(
|
||||
#[tauri::command]
|
||||
pub fn set_resume_playback_position(
|
||||
resume: bool,
|
||||
state: tauri::State<PlaybackPreferencesState>,
|
||||
state: tauri::State<'_, PlaybackPreferencesState>,
|
||||
) -> Result<(), String> {
|
||||
state.set_resume_playback_position(resume)
|
||||
}
|
||||
@@ -1,308 +1,47 @@
|
||||
//! Tray icon settings
|
||||
//!
|
||||
//! Stores user preferences for system tray behavior:
|
||||
//! - enable_tray: Show tray icon (requires restart)
|
||||
//! - minimize_to_tray: Hide to tray when minimizing
|
||||
//! - close_to_tray: Hide to tray when closing window
|
||||
|
||||
use log::info;
|
||||
use rusqlite::{params, Connection};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TraySettings {
|
||||
/// Show system tray icon (requires restart to take effect)
|
||||
pub enable_tray: bool,
|
||||
/// Hide window to tray when clicking minimize
|
||||
pub minimize_to_tray: bool,
|
||||
/// Hide window to tray instead of quitting when clicking close
|
||||
pub close_to_tray: bool,
|
||||
/// Tray icon variant override:
|
||||
/// - "auto" (default; follows system color scheme),
|
||||
/// - "mono-light" (light-coloured glyph for dark panels — useful
|
||||
/// on GNOME where the top bar is permanently dark),
|
||||
/// - "mono-dark" (dark-coloured glyph for light panels),
|
||||
/// - "color" (full colour vinyl logo).
|
||||
#[serde(default = "default_tray_icon_theme")]
|
||||
pub tray_icon_theme: String,
|
||||
}
|
||||
|
||||
fn default_tray_icon_theme() -> String {
|
||||
"auto".to_string()
|
||||
}
|
||||
|
||||
/// Coerce free-form values to the supported set. Anything outside the
|
||||
/// supported list falls back to "auto".
|
||||
///
|
||||
/// Legacy 1.2.9-pre values "light"/"dark" had inverted semantics
|
||||
/// relative to the labels users saw (picking "Light" got the dark
|
||||
/// glyph and vice-versa). They're remapped here to the value that
|
||||
/// matches the user's original intent.
|
||||
pub fn normalize_tray_icon_theme(input: &str) -> String {
|
||||
match input {
|
||||
"mono-light" | "mono-dark" | "color" | "auto" => input.to_string(),
|
||||
"light" => "mono-light".to_string(),
|
||||
"dark" => "mono-dark".to_string(),
|
||||
_ => "auto".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TraySettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enable_tray: true,
|
||||
minimize_to_tray: false,
|
||||
close_to_tray: false,
|
||||
tray_icon_theme: default_tray_icon_theme(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TraySettingsStore {
|
||||
conn: Connection,
|
||||
}
|
||||
|
||||
impl TraySettingsStore {
|
||||
fn open_at(dir: &Path, db_name: &str) -> Result<Self, String> {
|
||||
std::fs::create_dir_all(dir)
|
||||
.map_err(|e| format!("Failed to create data directory: {}", e))?;
|
||||
|
||||
let db_path = dir.join(db_name);
|
||||
let conn = Connection::open(&db_path)
|
||||
.map_err(|e| format!("Failed to open tray settings database: {}", e))?;
|
||||
|
||||
conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL;")
|
||||
.map_err(|e| format!("Failed to enable WAL for tray settings database: {}", e))?;
|
||||
|
||||
// Create table
|
||||
conn.execute_batch(
|
||||
"CREATE TABLE IF NOT EXISTS tray_settings (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
enable_tray INTEGER NOT NULL DEFAULT 1,
|
||||
minimize_to_tray INTEGER NOT NULL DEFAULT 0,
|
||||
close_to_tray INTEGER NOT NULL DEFAULT 0
|
||||
);",
|
||||
)
|
||||
.map_err(|e| format!("Failed to create tray settings table: {}", e))?;
|
||||
|
||||
// Migration: add tray_icon_theme column if it doesn't exist (older DBs).
|
||||
let has_theme_column: i64 = conn
|
||||
.query_row(
|
||||
"SELECT COUNT(*) FROM pragma_table_info('tray_settings') WHERE name = 'tray_icon_theme'",
|
||||
[],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.map_err(|e| format!("Failed to check tray_icon_theme column: {}", e))?;
|
||||
if has_theme_column == 0 {
|
||||
conn.execute_batch(
|
||||
"ALTER TABLE tray_settings ADD COLUMN tray_icon_theme TEXT NOT NULL DEFAULT 'auto';",
|
||||
)
|
||||
.map_err(|e| format!("Failed to add tray_icon_theme column: {}", e))?;
|
||||
}
|
||||
|
||||
// Insert default row if it doesn't exist
|
||||
conn.execute(
|
||||
"INSERT OR IGNORE INTO tray_settings (id, enable_tray, minimize_to_tray, close_to_tray, tray_icon_theme)
|
||||
VALUES (1, 1, 0, 0, 'auto')",
|
||||
[],
|
||||
)
|
||||
.map_err(|e| format!("Failed to insert default tray settings: {}", e))?;
|
||||
|
||||
info!("[TraySettings] Database initialized");
|
||||
|
||||
Ok(Self { conn })
|
||||
}
|
||||
|
||||
pub fn new() -> Result<Self, String> {
|
||||
let data_dir = dirs::data_dir()
|
||||
.ok_or("Could not determine data directory")?
|
||||
.join("qbz");
|
||||
Self::open_at(&data_dir, "tray_settings.db")
|
||||
}
|
||||
|
||||
pub fn new_at(base_dir: &Path) -> Result<Self, String> {
|
||||
Self::open_at(base_dir, "tray_settings.db")
|
||||
}
|
||||
|
||||
pub fn get_settings(&self) -> Result<TraySettings, String> {
|
||||
self.conn
|
||||
.query_row(
|
||||
"SELECT enable_tray, minimize_to_tray, close_to_tray, tray_icon_theme FROM tray_settings WHERE id = 1",
|
||||
[],
|
||||
|row| {
|
||||
let enable_tray: i32 = row.get(0)?;
|
||||
let minimize_to_tray: i32 = row.get(1)?;
|
||||
let close_to_tray: i32 = row.get(2)?;
|
||||
let tray_icon_theme: String = row.get(3)?;
|
||||
Ok(TraySettings {
|
||||
enable_tray: enable_tray != 0,
|
||||
minimize_to_tray: minimize_to_tray != 0,
|
||||
close_to_tray: close_to_tray != 0,
|
||||
tray_icon_theme: normalize_tray_icon_theme(&tray_icon_theme),
|
||||
})
|
||||
},
|
||||
)
|
||||
.map_err(|e| format!("Failed to get tray settings: {}", e))
|
||||
}
|
||||
|
||||
pub fn set_enable_tray(&self, value: bool) -> Result<(), String> {
|
||||
self.conn
|
||||
.execute(
|
||||
"UPDATE tray_settings SET enable_tray = ?1 WHERE id = 1",
|
||||
params![if value { 1 } else { 0 }],
|
||||
)
|
||||
.map_err(|e| format!("Failed to set enable_tray: {}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_minimize_to_tray(&self, value: bool) -> Result<(), String> {
|
||||
self.conn
|
||||
.execute(
|
||||
"UPDATE tray_settings SET minimize_to_tray = ?1 WHERE id = 1",
|
||||
params![if value { 1 } else { 0 }],
|
||||
)
|
||||
.map_err(|e| format!("Failed to set minimize_to_tray: {}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_close_to_tray(&self, value: bool) -> Result<(), String> {
|
||||
self.conn
|
||||
.execute(
|
||||
"UPDATE tray_settings SET close_to_tray = ?1 WHERE id = 1",
|
||||
params![if value { 1 } else { 0 }],
|
||||
)
|
||||
.map_err(|e| format!("Failed to set close_to_tray: {}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_tray_icon_theme(&self, value: &str) -> Result<(), String> {
|
||||
let normalized = normalize_tray_icon_theme(value);
|
||||
self.conn
|
||||
.execute(
|
||||
"UPDATE tray_settings SET tray_icon_theme = ?1 WHERE id = 1",
|
||||
params![normalized],
|
||||
)
|
||||
.map_err(|e| format!("Failed to set tray_icon_theme: {}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Global state wrapper for thread-safe access
|
||||
pub struct TraySettingsState {
|
||||
pub store: Arc<Mutex<Option<TraySettingsStore>>>,
|
||||
}
|
||||
|
||||
impl TraySettingsState {
|
||||
pub fn new() -> Result<Self, String> {
|
||||
let store = TraySettingsStore::new()?;
|
||||
Ok(Self {
|
||||
store: Arc::new(Mutex::new(Some(store))),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_empty() -> Self {
|
||||
Self {
|
||||
store: Arc::new(Mutex::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_at(&self, base_dir: &Path) -> Result<(), String> {
|
||||
let new_store = TraySettingsStore::new_at(base_dir)?;
|
||||
let mut guard = self
|
||||
.store
|
||||
.lock()
|
||||
.map_err(|_| "Failed to lock tray settings store".to_string())?;
|
||||
*guard = Some(new_store);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn teardown(&self) -> Result<(), String> {
|
||||
let mut guard = self
|
||||
.store
|
||||
.lock()
|
||||
.map_err(|_| "Failed to lock tray settings store".to_string())?;
|
||||
*guard = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_settings(&self) -> Result<TraySettings, String> {
|
||||
let guard = self
|
||||
.store
|
||||
.lock()
|
||||
.map_err(|_| "Failed to lock tray settings store".to_string())?;
|
||||
let store = guard.as_ref().ok_or("No active session - please log in")?;
|
||||
store.get_settings()
|
||||
}
|
||||
|
||||
pub fn set_enable_tray(&self, value: bool) -> Result<(), String> {
|
||||
let guard = self
|
||||
.store
|
||||
.lock()
|
||||
.map_err(|_| "Failed to lock tray settings store".to_string())?;
|
||||
let store = guard.as_ref().ok_or("No active session - please log in")?;
|
||||
store.set_enable_tray(value)
|
||||
}
|
||||
|
||||
pub fn set_minimize_to_tray(&self, value: bool) -> Result<(), String> {
|
||||
let guard = self
|
||||
.store
|
||||
.lock()
|
||||
.map_err(|_| "Failed to lock tray settings store".to_string())?;
|
||||
let store = guard.as_ref().ok_or("No active session - please log in")?;
|
||||
store.set_minimize_to_tray(value)
|
||||
}
|
||||
|
||||
pub fn set_close_to_tray(&self, value: bool) -> Result<(), String> {
|
||||
let guard = self
|
||||
.store
|
||||
.lock()
|
||||
.map_err(|_| "Failed to lock tray settings store".to_string())?;
|
||||
let store = guard.as_ref().ok_or("No active session - please log in")?;
|
||||
store.set_close_to_tray(value)
|
||||
}
|
||||
|
||||
pub fn set_tray_icon_theme(&self, value: &str) -> Result<(), String> {
|
||||
let guard = self
|
||||
.store
|
||||
.lock()
|
||||
.map_err(|_| "Failed to lock tray settings store".to_string())?;
|
||||
let store = guard.as_ref().ok_or("No active session - please log in")?;
|
||||
store.set_tray_icon_theme(value)
|
||||
}
|
||||
}
|
||||
|
||||
// Tauri commands
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_tray_settings(state: tauri::State<TraySettingsState>) -> Result<TraySettings, String> {
|
||||
state.get_settings()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn set_enable_tray(value: bool, state: tauri::State<TraySettingsState>) -> Result<(), String> {
|
||||
info!(
|
||||
"[TraySettings] Setting enable_tray to {} (restart required)",
|
||||
value
|
||||
);
|
||||
state.set_enable_tray(value)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn set_minimize_to_tray(
|
||||
value: bool,
|
||||
state: tauri::State<TraySettingsState>,
|
||||
) -> Result<(), String> {
|
||||
info!("[TraySettings] Setting minimize_to_tray to {}", value);
|
||||
state.set_minimize_to_tray(value)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn set_close_to_tray(
|
||||
value: bool,
|
||||
state: tauri::State<TraySettingsState>,
|
||||
) -> Result<(), String> {
|
||||
info!("[TraySettings] Setting close_to_tray to {}", value);
|
||||
state.set_close_to_tray(value)
|
||||
}
|
||||
//! Tauri commands for tray settings.
|
||||
//!
|
||||
//! Persisted tray preferences live in `qbz-app`. Runtime tray creation,
|
||||
//! icon updates, window behavior, and emitted events remain Tauri-owned.
|
||||
|
||||
use log::info;
|
||||
|
||||
pub use qbz_app::settings::tray::{
|
||||
normalize_tray_icon_theme, TraySettings, TraySettingsState, TraySettingsStore,
|
||||
};
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_tray_settings(
|
||||
state: tauri::State<'_, TraySettingsState>,
|
||||
) -> Result<TraySettings, String> {
|
||||
state.get_settings()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn set_enable_tray(
|
||||
value: bool,
|
||||
state: tauri::State<'_, TraySettingsState>,
|
||||
) -> Result<(), String> {
|
||||
info!(
|
||||
"[TraySettings] Setting enable_tray to {} (restart required)",
|
||||
value
|
||||
);
|
||||
state.set_enable_tray(value)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn set_minimize_to_tray(
|
||||
value: bool,
|
||||
state: tauri::State<'_, TraySettingsState>,
|
||||
) -> Result<(), String> {
|
||||
info!("[TraySettings] Setting minimize_to_tray to {}", value);
|
||||
state.set_minimize_to_tray(value)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn set_close_to_tray(
|
||||
value: bool,
|
||||
state: tauri::State<'_, TraySettingsState>,
|
||||
) -> Result<(), String> {
|
||||
info!("[TraySettings] Setting close_to_tray to {}", value);
|
||||
state.set_close_to_tray(value)
|
||||
}
|
||||
Reference in new issue
Block a user