diff --git a/apps/desktop/src/components/editor/EditorSettingsDialog.vue b/apps/desktop/src/components/editor/EditorSettingsDialog.vue index c717586a5..d66e8c14c 100644 --- a/apps/desktop/src/components/editor/EditorSettingsDialog.vue +++ b/apps/desktop/src/components/editor/EditorSettingsDialog.vue @@ -60,7 +60,7 @@ const editTheme = ref(settingsStore.editorSettings.theme); const editExecuteMode = ref(settingsStore.editorSettings.executeMode); const editWordWrap = ref(settingsStore.editorSettings.wordWrap); const editAppLayout = ref(settingsStore.editorSettings.appLayout); -const editRunInBackground = ref(settingsStore.desktopSettings.run_in_background); +const editShowTrayIcon = ref(settingsStore.desktopSettings.show_tray_icon); const editRedisScanPageSize = ref(settingsStore.editorSettings.redisScanPageSize); const editShortcuts = ref(normalizeShortcutSettings(settingsStore.editorSettings.shortcuts)); const editSidebarActivation = ref(settingsStore.editorSettings.sidebarActivation); @@ -124,7 +124,7 @@ watch( editExecuteMode.value = settingsStore.editorSettings.executeMode; editWordWrap.value = settingsStore.editorSettings.wordWrap; editAppLayout.value = settingsStore.editorSettings.appLayout; - editRunInBackground.value = settingsStore.desktopSettings.run_in_background; + editShowTrayIcon.value = settingsStore.desktopSettings.show_tray_icon; editRedisScanPageSize.value = settingsStore.editorSettings.redisScanPageSize; editShortcuts.value = normalizeShortcutSettings(settingsStore.editorSettings.shortcuts); editSidebarActivation.value = settingsStore.editorSettings.sidebarActivation; @@ -155,7 +155,7 @@ function hasChanges(): boolean { editExecuteMode.value !== settingsStore.editorSettings.executeMode || editWordWrap.value !== settingsStore.editorSettings.wordWrap || editAppLayout.value !== settingsStore.editorSettings.appLayout || - editRunInBackground.value !== settingsStore.desktopSettings.run_in_background || + editShowTrayIcon.value !== settingsStore.desktopSettings.show_tray_icon || editRedisScanPageSize.value !== settingsStore.editorSettings.redisScanPageSize || JSON.stringify(editShortcuts.value) !== JSON.stringify(settingsStore.editorSettings.shortcuts) || editSidebarActivation.value !== settingsStore.editorSettings.sidebarActivation || @@ -181,7 +181,7 @@ async function applySettings() { sidebarHiddenTablePrefixes: normalizeSidebarHiddenTablePrefixes(editSidebarHiddenTablePrefixes.value), }); await settingsStore.updateDesktopSettings({ - run_in_background: editRunInBackground.value, + show_tray_icon: editShowTrayIcon.value, }); emit("update:open", false); } @@ -193,7 +193,7 @@ function resetDefaults() { editExecuteMode.value = DEFAULT_EDITOR_SETTINGS.executeMode; editWordWrap.value = DEFAULT_EDITOR_SETTINGS.wordWrap; editAppLayout.value = DEFAULT_EDITOR_SETTINGS.appLayout; - editRunInBackground.value = DEFAULT_DESKTOP_SETTINGS.run_in_background; + editShowTrayIcon.value = DEFAULT_DESKTOP_SETTINGS.show_tray_icon; editRedisScanPageSize.value = DEFAULT_EDITOR_SETTINGS.redisScanPageSize; editShortcuts.value = normalizeShortcutSettings(DEFAULT_EDITOR_SETTINGS.shortcuts); editSidebarActivation.value = DEFAULT_EDITOR_SETTINGS.sidebarActivation; @@ -301,7 +301,7 @@ watch( confirmNewPassword.value = ""; await settingsStore.initAiConfig(); await settingsStore.initDesktopSettings(); - editRunInBackground.value = settingsStore.desktopSettings.run_in_background; + editShowTrayIcon.value = settingsStore.desktopSettings.show_tray_icon; syncAiEditState(); } }, @@ -840,10 +840,10 @@ watch( class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2" >
- -

{{ t("settings.runInBackgroundDescription") }}

+ +

{{ t("settings.showTrayIconDescription") }}

- + diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index 5fb636e57..08627ecd9 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -1194,9 +1194,9 @@ export default { appLayoutSeparatedDescription: "Clear panel separation with block-style tabs.", appLayoutClassic: "Classic compact", appLayoutClassicDescription: "Continuous tab bar with compact panel headers.", - runInBackground: "Keep running after closing window", - runInBackgroundDescription: - "When enabled, DBX shows a tray/menu bar icon and closing the window keeps it in the background. When disabled, no icon is shown and closing the window quits the app.", + showTrayIcon: "Show tray/menu bar icon", + showTrayIconDescription: + "When disabled, no icon is shown, but closing the window still hides DBX in the background as before.", sidebarActivation: "Sidebar activation", sidebarActivationSingle: "Single click", sidebarActivationSingleDescription: "Open actionable sidebar items with one click.", diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index b2aab95c7..f2e8fce41 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -1092,9 +1092,9 @@ export default { appLayoutSeparatedDescription: "Separación clara de paneles con pestañas en bloque.", appLayoutClassic: "Clásico compacto", appLayoutClassicDescription: "Barra de pestañas continua con encabezados de panel compactos.", - runInBackground: "Seguir ejecutando al cerrar la ventana", - runInBackgroundDescription: - "Si está activado, DBX muestra un icono en la bandeja/barra de menú y cerrar la ventana lo deja en segundo plano. Si está desactivado, no se muestra el icono y cerrar la ventana sale de la app.", + showTrayIcon: "Mostrar icono en bandeja/barra de menú", + showTrayIconDescription: + "Si está desactivado, no se muestra el icono, pero cerrar la ventana sigue ocultando DBX en segundo plano como antes.", sidebarActivation: "Activación de la barra lateral", sidebarActivationSingle: "Un clic", sidebarActivationSingleDescription: "Abrir elementos accionables de la barra lateral con un clic.", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index 5051d719a..c689d67a7 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -1172,9 +1172,8 @@ export default { appLayoutSeparatedDescription: "面板分隔更清晰,标签页为块状样式。", appLayoutClassic: "经典紧凑", appLayoutClassicDescription: "使用连续标签栏和更紧凑的面板标题。", - runInBackground: "关闭窗口后后台驻留", - runInBackgroundDescription: - "开启时显示系统托盘/菜单栏图标,关闭窗口会隐藏到后台;关闭后不显示图标,关闭窗口将退出应用。", + showTrayIcon: "显示系统托盘/菜单栏图标", + showTrayIconDescription: "关闭后不显示图标,但关闭窗口仍会像之前一样隐藏到后台。", sidebarActivation: "侧边栏打开方式", sidebarActivationSingle: "单击打开", sidebarActivationSingleDescription: "单击即可打开侧边栏中的可操作项目。", diff --git a/apps/desktop/src/lib/http.ts b/apps/desktop/src/lib/http.ts index 8a4c0ea8e..c9cf6ffbc 100644 --- a/apps/desktop/src/lib/http.ts +++ b/apps/desktop/src/lib/http.ts @@ -468,7 +468,7 @@ export async function loadAiConfig(): Promise { } export async function loadDesktopSettings(): Promise { - return { run_in_background: true }; + return { show_tray_icon: true }; } export async function saveDesktopSettings(_settings: DesktopSettings): Promise { diff --git a/apps/desktop/src/lib/tauri.ts b/apps/desktop/src/lib/tauri.ts index faa56cd1d..4192f9776 100644 --- a/apps/desktop/src/lib/tauri.ts +++ b/apps/desktop/src/lib/tauri.ts @@ -41,7 +41,7 @@ export interface JavaRuntimeConfig { } export interface DesktopSettings { - run_in_background: boolean; + show_tray_icon: boolean; } export interface DriverInstallProgress { diff --git a/apps/desktop/src/stores/settingsStore.ts b/apps/desktop/src/stores/settingsStore.ts index 93679c274..22d5a5252 100644 --- a/apps/desktop/src/stores/settingsStore.ts +++ b/apps/desktop/src/stores/settingsStore.ts @@ -35,11 +35,11 @@ export interface AiConfig { } export interface DesktopSettings { - run_in_background: boolean; + show_tray_icon: boolean; } export const DEFAULT_DESKTOP_SETTINGS: DesktopSettings = { - run_in_background: true, + show_tray_icon: true, }; export interface AiProviderPreset extends Omit { diff --git a/crates/dbx-core/src/storage.rs b/crates/dbx-core/src/storage.rs index 303e5854e..e1107c276 100644 --- a/crates/dbx-core/src/storage.rs +++ b/crates/dbx-core/src/storage.rs @@ -15,12 +15,12 @@ pub struct Storage { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct DesktopSettings { - pub run_in_background: bool, + pub show_tray_icon: bool, } impl Default for DesktopSettings { fn default() -> Self { - Self { run_in_background: true } + Self { show_tray_icon: true } } } @@ -326,17 +326,18 @@ impl Storage { pub async fn save_desktop_settings(&self, desktop_settings: &DesktopSettings) -> Result<(), String> { let mut settings = self.load_app_settings_json().await?; - settings.insert("run_in_background".to_string(), serde_json::Value::Bool(desktop_settings.run_in_background)); + settings.remove("run_in_background"); + settings.insert("show_tray_icon".to_string(), serde_json::Value::Bool(desktop_settings.show_tray_icon)); self.save_app_settings_json(&settings).await } pub async fn load_desktop_settings(&self) -> Result { let settings = self.load_app_settings_json().await?; Ok(DesktopSettings { - run_in_background: settings - .get("run_in_background") + show_tray_icon: settings + .get("show_tray_icon") .and_then(|value| value.as_bool()) - .unwrap_or_else(|| DesktopSettings::default().run_in_background), + .unwrap_or_else(|| DesktopSettings::default().show_tray_icon), }) } } @@ -988,7 +989,18 @@ mod tests { let path = temp_db_path("desktop-settings-default"); let storage = Storage::open(&path).await.unwrap(); - assert_eq!(storage.load_desktop_settings().await.unwrap(), DesktopSettings { run_in_background: true }); + assert_eq!(storage.load_desktop_settings().await.unwrap(), DesktopSettings { show_tray_icon: true }); + } + + #[tokio::test] + async fn desktop_settings_ignore_legacy_background_preference() { + let path = temp_db_path("desktop-settings-legacy-background"); + let storage = Storage::open(&path).await.unwrap(); + let mut settings = serde_json::Map::new(); + settings.insert("run_in_background".to_string(), serde_json::Value::Bool(false)); + storage.save_app_settings_json(&settings).await.unwrap(); + + assert_eq!(storage.load_desktop_settings().await.unwrap(), DesktopSettings { show_tray_icon: true }); } #[tokio::test] @@ -997,10 +1009,25 @@ mod tests { let storage = Storage::open(&path).await.unwrap(); storage.save_password_hash("hash-1").await.unwrap(); - storage.save_desktop_settings(&DesktopSettings { run_in_background: false }).await.unwrap(); + storage.save_desktop_settings(&DesktopSettings { show_tray_icon: false }).await.unwrap(); assert_eq!(storage.load_password_hash().await.unwrap(), Some("hash-1".to_string())); - assert_eq!(storage.load_desktop_settings().await.unwrap(), DesktopSettings { run_in_background: false }); + assert_eq!(storage.load_desktop_settings().await.unwrap(), DesktopSettings { show_tray_icon: false }); + } + + #[tokio::test] + async fn desktop_settings_save_removes_legacy_background_preference() { + let path = temp_db_path("desktop-settings-remove-legacy-background"); + let storage = Storage::open(&path).await.unwrap(); + let mut settings = serde_json::Map::new(); + settings.insert("run_in_background".to_string(), serde_json::Value::Bool(false)); + storage.save_app_settings_json(&settings).await.unwrap(); + + storage.save_desktop_settings(&DesktopSettings { show_tray_icon: true }).await.unwrap(); + + let settings = storage.load_app_settings_json().await.unwrap(); + assert_eq!(settings.get("run_in_background"), None); + assert_eq!(settings.get("show_tray_icon").and_then(|value| value.as_bool()), Some(true)); } #[tokio::test] @@ -1008,10 +1035,10 @@ mod tests { let path = temp_db_path("password-preserve-desktop-settings"); let storage = Storage::open(&path).await.unwrap(); - storage.save_desktop_settings(&DesktopSettings { run_in_background: false }).await.unwrap(); + storage.save_desktop_settings(&DesktopSettings { show_tray_icon: false }).await.unwrap(); storage.save_password_hash("hash-2").await.unwrap(); assert_eq!(storage.load_password_hash().await.unwrap(), Some("hash-2".to_string())); - assert_eq!(storage.load_desktop_settings().await.unwrap(), DesktopSettings { run_in_background: false }); + assert_eq!(storage.load_desktop_settings().await.unwrap(), DesktopSettings { show_tray_icon: false }); } } diff --git a/src-tauri/src/commands/app_settings.rs b/src-tauri/src/commands/app_settings.rs index d74d79a81..e3a3bd1be 100644 --- a/src-tauri/src/commands/app_settings.rs +++ b/src-tauri/src/commands/app_settings.rs @@ -1,10 +1,10 @@ use std::sync::Arc; use dbx_core::storage::DesktopSettings; -use tauri::{AppHandle, Manager, State}; +use tauri::{AppHandle, State}; use super::connection::AppState; -use crate::{apply_desktop_tray_preference, WindowBehaviorState}; +use crate::apply_desktop_tray_preference; #[tauri::command] pub async fn load_desktop_settings(state: State<'_, Arc>) -> Result { @@ -18,8 +18,8 @@ pub async fn save_desktop_settings( settings: DesktopSettings, ) -> Result<(), String> { state.storage.save_desktop_settings(&settings).await?; - if let Some(window_behavior) = app.try_state::() { - window_behavior.set_run_in_background(settings.run_in_background); + if let Err(err) = apply_desktop_tray_preference(&app, settings.show_tray_icon) { + eprintln!("Failed to apply desktop tray preference: {err}"); } - apply_desktop_tray_preference(&app, settings.run_in_background).map_err(|err| err.to_string()) + Ok(()) } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index f0d99718d..f1cd292c3 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -5,11 +5,8 @@ mod models; mod window_state_guard; use commands::connection::AppState; -use dbx_core::storage::{DesktopSettings, Storage}; -use std::sync::{ - atomic::{AtomicBool, Ordering}, - Arc, -}; +use dbx_core::storage::Storage; +use std::sync::Arc; use std::time::Instant; use tauri::{ menu::MenuBuilder, @@ -21,30 +18,12 @@ use tauri_plugin_deep_link::DeepLinkExt; const DESKTOP_TRAY_ID: &str = "main-tray"; -pub(crate) struct WindowBehaviorState { - run_in_background: AtomicBool, +fn should_hide_window_on_close(target_os: &str) -> bool { + matches!(target_os, "macos" | "windows") } -impl WindowBehaviorState { - fn new(settings: &DesktopSettings) -> Self { - Self { run_in_background: AtomicBool::new(settings.run_in_background) } - } - - pub(crate) fn run_in_background(&self) -> bool { - self.run_in_background.load(Ordering::Relaxed) - } - - pub(crate) fn set_run_in_background(&self, value: bool) { - self.run_in_background.store(value, Ordering::Relaxed); - } -} - -fn should_hide_window_on_close(target_os: &str, run_in_background: bool) -> bool { - run_in_background && matches!(target_os, "macos" | "windows") -} - -fn should_setup_desktop_tray(target_os: &str, run_in_background: bool) -> bool { - run_in_background && matches!(target_os, "macos" | "windows") +fn should_setup_desktop_tray(target_os: &str, show_tray_icon: bool) -> bool { + show_tray_icon && matches!(target_os, "macos" | "windows") } fn should_show_main_window_after_setup() -> bool { @@ -75,6 +54,10 @@ fn setup_desktop_tray>(manager: &M) -> tauri::R let menu = MenuBuilder::new(manager).text("show", "Show DBX").separator().text("quit", "Quit DBX").build()?; let mut tray = TrayIconBuilder::::with_id(DESKTOP_TRAY_ID).tooltip("DBX").menu(&menu).show_menu_on_left_click(false); + #[cfg(target_os = "macos")] + { + tray = tray.title("DBX"); + } if let Some(icon) = manager.app_handle().default_window_icon().cloned() { tray = tray.icon(icon); @@ -97,13 +80,13 @@ fn setup_desktop_tray>(manager: &M) -> tauri::R Ok(()) } -pub(crate) fn apply_desktop_tray_preference(app: &tauri::AppHandle, run_in_background: bool) -> tauri::Result<()> { - if should_setup_desktop_tray(std::env::consts::OS, run_in_background) { - if app.tray_by_id(DESKTOP_TRAY_ID).is_none() { +pub(crate) fn apply_desktop_tray_preference(app: &tauri::AppHandle, show_tray_icon: bool) -> tauri::Result<()> { + if matches!(std::env::consts::OS, "macos" | "windows") { + if let Some(tray) = app.tray_by_id(DESKTOP_TRAY_ID) { + tray.set_visible(show_tray_icon)?; + } else if show_tray_icon { setup_desktop_tray(app)?; } - } else { - let _ = app.remove_tray_by_id(DESKTOP_TRAY_ID); } Ok(()) } @@ -114,15 +97,13 @@ mod tests { #[test] fn hides_window_on_close_for_windows_and_macos() { - assert!(should_hide_window_on_close("windows", true)); - assert!(should_hide_window_on_close("macos", true)); - assert!(!should_hide_window_on_close("windows", false)); - assert!(!should_hide_window_on_close("macos", false)); + assert!(should_hide_window_on_close("windows")); + assert!(should_hide_window_on_close("macos")); } #[test] fn does_not_hide_window_on_close_for_other_platforms() { - assert!(!should_hide_window_on_close("linux", true)); + assert!(!should_hide_window_on_close("linux")); } #[test] @@ -134,10 +115,25 @@ mod tests { assert!(!should_setup_desktop_tray("linux", true)); let source = include_str!("lib.rs"); assert!(source.contains( - "if should_setup_desktop_tray(std::env::consts::OS, desktop_settings.run_in_background) {\n setup_desktop_tray(app)?;" + "if should_setup_desktop_tray(std::env::consts::OS, desktop_settings.show_tray_icon) {\n setup_desktop_tray(app)?;" )); } + #[test] + fn tray_preference_hides_existing_tray_instead_of_removing_it() { + let source = include_str!("lib.rs"); + assert!(source.contains("tray.set_visible(show_tray_icon)?;")); + let remove_call = concat!("remove", "_tray_by_id"); + assert!(!source.contains(remove_call)); + } + + #[test] + fn desktop_settings_save_treats_runtime_tray_update_as_best_effort() { + let source = include_str!("commands/app_settings.rs"); + assert!(source.contains("if let Err(err) = apply_desktop_tray_preference")); + assert!(!source.contains("map_err(|err| err.to_string())")); + } + #[test] fn shows_main_window_after_regular_startup_setup() { assert!(should_show_main_window_after_setup()); @@ -206,7 +202,6 @@ pub fn run() { env!("CARGO_PKG_VERSION"), )); app.manage(state.clone()); - app.manage(WindowBehaviorState::new(&desktop_settings)); app.manage(commands::external_sql::ExternalSqlOpenState::default()); app.manage(commands::deep_link::DeepLinkOpenState::default()); let startup_links = commands::deep_link::connection_deep_links_from_args(std::env::args().skip(1)); @@ -222,7 +217,7 @@ pub fn run() { let _ = window.set_decorations(false); } } - if should_setup_desktop_tray(std::env::consts::OS, desktop_settings.run_in_background) { + if should_setup_desktop_tray(std::env::consts::OS, desktop_settings.show_tray_icon) { setup_desktop_tray(app)?; } window_state_guard::enforce_main_window_bounds(app.handle()); @@ -236,16 +231,9 @@ pub fn run() { }) .on_window_event(|window, event| { if let tauri::WindowEvent::CloseRequested { api, .. } = event { - let run_in_background = window - .app_handle() - .try_state::() - .map(|state| state.run_in_background()) - .unwrap_or_else(|| DesktopSettings::default().run_in_background); - if should_hide_window_on_close(std::env::consts::OS, run_in_background) { + if should_hide_window_on_close(std::env::consts::OS) { let _ = window.hide(); api.prevent_close(); - } else { - window.app_handle().exit(0); } } })