From dc86ffb342af52b7081882a528ff33b957192b33 Mon Sep 17 00:00:00 2001 From: g-k-s-03 Date: Sun, 16 Aug 2026 00:37:34 +0530 Subject: [PATCH 1/3] feat: add start minimized toggle for autostart Lets users disable the minimize-on-boot behavior added in the autostart feature (issue 817) while keeping launch at startup on. The toggle is only shown when Launch at Startup is enabled and its value is persisted with tauri-plugin-store so the main.rs startup handler can read it before deciding whether to hide the window. Signed-off-by: g-k-s-03 --- frontend/src-tauri/src/main.rs | 33 +++++++++- .../components/SystemSettingsCard.tsx | 66 +++++++++++++++++++ 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/frontend/src-tauri/src/main.rs b/frontend/src-tauri/src/main.rs index b74c7d65c..1a30311d2 100644 --- a/frontend/src-tauri/src/main.rs +++ b/frontend/src-tauri/src/main.rs @@ -15,6 +15,7 @@ use tauri_plugin_store::StoreExt; const STORE_PATH: &str = "settings.json"; const CLOSE_TO_TRAY_KEY: &str = "close_to_tray"; +const START_MINIMIZED_KEY: &str = "start_minimized"; const ENDPOINTS: [(&str, &str, &str); 2] = [ ( @@ -274,6 +275,22 @@ fn set_close_to_tray(app: tauri::AppHandle, enabled: bool) -> Result<(), String> store.save().map_err(|e| e.to_string()) } +#[tauri::command] +fn get_start_minimized(app: tauri::AppHandle) -> Result { + let store = app.store(STORE_PATH).map_err(|e| e.to_string())?; + Ok(store + .get(START_MINIMIZED_KEY) + .and_then(|v| v.as_bool()) + .unwrap_or(true)) // default: start minimized to tray, matching prior behavior +} + +#[tauri::command] +fn set_start_minimized(app: tauri::AppHandle, enabled: bool) -> Result<(), String> { + let store = app.store(STORE_PATH).map_err(|e| e.to_string())?; + store.set(START_MINIMIZED_KEY, enabled); + store.save().map_err(|e| e.to_string()) +} + fn main() { tauri::Builder::default() // Auto-start: pass --minimized so the window starts hidden when launched at boot @@ -296,9 +313,19 @@ fn main() { prod(app.handle(), &resource_path)?; // When auto-started at boot (--minimized flag), keep the window hidden + // unless the user has opted out via the "Start Minimized" preference. if std::env::args().any(|a| a == "--minimized") { - if let Some(window) = app.get_webview_window("main") { - let _ = window.hide(); + let start_minimized = app + .store(STORE_PATH) + .ok() + .and_then(|s| s.get(START_MINIMIZED_KEY)) + .and_then(|v| v.as_bool()) + .unwrap_or(true); // default: start minimized to tray + + if start_minimized { + if let Some(window) = app.get_webview_window("main") { + let _ = window.hide(); + } } } @@ -361,6 +388,8 @@ fn main() { is_autostart_enabled, get_close_to_tray, set_close_to_tray, + get_start_minimized, + set_start_minimized, ]) .on_window_event(on_window_event) .build(tauri::generate_context!()) diff --git a/frontend/src/pages/SettingsPage/components/SystemSettingsCard.tsx b/frontend/src/pages/SettingsPage/components/SystemSettingsCard.tsx index 4a05be707..cc0f7b8be 100644 --- a/frontend/src/pages/SettingsPage/components/SystemSettingsCard.tsx +++ b/frontend/src/pages/SettingsPage/components/SystemSettingsCard.tsx @@ -7,9 +7,11 @@ const SystemSettingsCard: React.FC = () => { // null = unknown / error reading state const [autostart, setAutostart] = useState(null); const [closeToTray, setCloseToTray] = useState(null); + const [startMinimized, setStartMinimized] = useState(null); const [loading, setLoading] = useState(true); const [pending, setPending] = useState(false); const [pendingCloseToTray, setPendingCloseToTray] = useState(false); + const [pendingStartMinimized, setPendingStartMinimized] = useState(false); useEffect(() => { Promise.all([ @@ -19,6 +21,9 @@ const SystemSettingsCard: React.FC = () => { invoke('get_close_to_tray') .then(setCloseToTray) .catch(() => setCloseToTray(null)), + invoke('get_start_minimized') + .then(setStartMinimized) + .catch(() => setStartMinimized(null)), ]).finally(() => setLoading(false)); }, []); @@ -50,6 +55,20 @@ const SystemSettingsCard: React.FC = () => { } }; + const handleStartMinimizedToggle = async () => { + if (startMinimized === null) return; + const next = !startMinimized; + setPendingStartMinimized(true); + try { + await invoke('set_start_minimized', { enabled: next }); + setStartMinimized(next); + } catch (err) { + console.error('Failed to toggle start minimized:', err); + } finally { + setPendingStartMinimized(false); + } + }; + const isDisabled = loading || pending || autostart === null; const isChecked = autostart === true; @@ -57,6 +76,10 @@ const SystemSettingsCard: React.FC = () => { loading || pendingCloseToTray || closeToTray === null; const closeToTrayChecked = closeToTray === true; + const startMinimizedDisabled = + loading || pendingStartMinimized || startMinimized === null; + const startMinimizedChecked = startMinimized === true; + return ( { + {isChecked && ( +
+
+
+ Start minimized +
+
+ When enabled, PictoPy starts silently in the system tray on boot. + When disabled, the main window opens on boot instead. +
+
+ + +
+ )} +
From 5fd653640d26dec62b649fb70683e6376a83a696 Mon Sep 17 00:00:00 2001 From: g-k-s-03 Date: Sun, 16 Aug 2026 01:20:26 +0530 Subject: [PATCH 2/3] fix: address CodeRabbit review comments on start minimized toggle Extract a shared SettingSwitchRow component so the autostart, start-minimized, and close-to-tray rows no longer duplicate the switch markup. Make the Launch at startup description reflect the current start-minimized preference instead of always mentioning the tray. Move the settings-store commands out of main.rs into a new services::settings module, matching how tunnel commands are already grouped under services. Signed-off-by: g-k-s-03 --- frontend/src-tauri/src/main.rs | 45 +----- frontend/src-tauri/src/services/mod.rs | 1 + frontend/src-tauri/src/services/settings.rs | 37 +++++ .../components/SettingSwitchRow.tsx | 85 ++++++++++ .../components/SystemSettingsCard.tsx | 146 ++++-------------- 5 files changed, 157 insertions(+), 157 deletions(-) create mode 100644 frontend/src-tauri/src/services/settings.rs create mode 100644 frontend/src/pages/SettingsPage/components/SettingSwitchRow.tsx diff --git a/frontend/src-tauri/src/main.rs b/frontend/src-tauri/src/main.rs index 1a30311d2..008fc0721 100644 --- a/frontend/src-tauri/src/main.rs +++ b/frontend/src-tauri/src/main.rs @@ -3,6 +3,7 @@ mod services; +use services::settings::{CLOSE_TO_TRAY_KEY, START_MINIMIZED_KEY, STORE_PATH}; use sysinfo::System; use tauri::menu::{Menu, MenuItem, PredefinedMenuItem}; use tauri::path::BaseDirectory; @@ -13,10 +14,6 @@ use tauri_plugin_autostart::ManagerExt; use tauri_plugin_shell::ShellExt; use tauri_plugin_store::StoreExt; -const STORE_PATH: &str = "settings.json"; -const CLOSE_TO_TRAY_KEY: &str = "close_to_tray"; -const START_MINIMIZED_KEY: &str = "start_minimized"; - const ENDPOINTS: [(&str, &str, &str); 2] = [ ( "BACKEND", @@ -259,38 +256,6 @@ fn is_autostart_enabled(app: tauri::AppHandle) -> Result { app.autolaunch().is_enabled().map_err(|e| e.to_string()) } -#[tauri::command] -fn get_close_to_tray(app: tauri::AppHandle) -> Result { - let store = app.store(STORE_PATH).map_err(|e| e.to_string())?; - Ok(store - .get(CLOSE_TO_TRAY_KEY) - .and_then(|v| v.as_bool()) - .unwrap_or(true)) -} - -#[tauri::command] -fn set_close_to_tray(app: tauri::AppHandle, enabled: bool) -> Result<(), String> { - let store = app.store(STORE_PATH).map_err(|e| e.to_string())?; - store.set(CLOSE_TO_TRAY_KEY, enabled); - store.save().map_err(|e| e.to_string()) -} - -#[tauri::command] -fn get_start_minimized(app: tauri::AppHandle) -> Result { - let store = app.store(STORE_PATH).map_err(|e| e.to_string())?; - Ok(store - .get(START_MINIMIZED_KEY) - .and_then(|v| v.as_bool()) - .unwrap_or(true)) // default: start minimized to tray, matching prior behavior -} - -#[tauri::command] -fn set_start_minimized(app: tauri::AppHandle, enabled: bool) -> Result<(), String> { - let store = app.store(STORE_PATH).map_err(|e| e.to_string())?; - store.set(START_MINIMIZED_KEY, enabled); - store.save().map_err(|e| e.to_string()) -} - fn main() { tauri::Builder::default() // Auto-start: pass --minimized so the window starts hidden when launched at boot @@ -386,10 +351,10 @@ fn main() { enable_autostart, disable_autostart, is_autostart_enabled, - get_close_to_tray, - set_close_to_tray, - get_start_minimized, - set_start_minimized, + services::settings::get_close_to_tray, + services::settings::set_close_to_tray, + services::settings::get_start_minimized, + services::settings::set_start_minimized, ]) .on_window_event(on_window_event) .build(tauri::generate_context!()) diff --git a/frontend/src-tauri/src/services/mod.rs b/frontend/src-tauri/src/services/mod.rs index a8948ab45..8b6f2356e 100644 --- a/frontend/src-tauri/src/services/mod.rs +++ b/frontend/src-tauri/src/services/mod.rs @@ -1,3 +1,4 @@ +pub mod settings; pub mod tunnel; use tauri::path::BaseDirectory; diff --git a/frontend/src-tauri/src/services/settings.rs b/frontend/src-tauri/src/services/settings.rs new file mode 100644 index 000000000..79991badc --- /dev/null +++ b/frontend/src-tauri/src/services/settings.rs @@ -0,0 +1,37 @@ +use tauri_plugin_store::StoreExt; + +pub const STORE_PATH: &str = "settings.json"; +pub const CLOSE_TO_TRAY_KEY: &str = "close_to_tray"; +pub const START_MINIMIZED_KEY: &str = "start_minimized"; + +#[tauri::command] +pub fn get_close_to_tray(app: tauri::AppHandle) -> Result { + let store = app.store(STORE_PATH).map_err(|e| e.to_string())?; + Ok(store + .get(CLOSE_TO_TRAY_KEY) + .and_then(|v| v.as_bool()) + .unwrap_or(true)) +} + +#[tauri::command] +pub fn set_close_to_tray(app: tauri::AppHandle, enabled: bool) -> Result<(), String> { + let store = app.store(STORE_PATH).map_err(|e| e.to_string())?; + store.set(CLOSE_TO_TRAY_KEY, enabled); + store.save().map_err(|e| e.to_string()) +} + +#[tauri::command] +pub fn get_start_minimized(app: tauri::AppHandle) -> Result { + let store = app.store(STORE_PATH).map_err(|e| e.to_string())?; + Ok(store + .get(START_MINIMIZED_KEY) + .and_then(|v| v.as_bool()) + .unwrap_or(true)) // default: start minimized to tray, matching prior behavior +} + +#[tauri::command] +pub fn set_start_minimized(app: tauri::AppHandle, enabled: bool) -> Result<(), String> { + let store = app.store(STORE_PATH).map_err(|e| e.to_string())?; + store.set(START_MINIMIZED_KEY, enabled); + store.save().map_err(|e| e.to_string()) +} diff --git a/frontend/src/pages/SettingsPage/components/SettingSwitchRow.tsx b/frontend/src/pages/SettingsPage/components/SettingSwitchRow.tsx new file mode 100644 index 000000000..50a023b4d --- /dev/null +++ b/frontend/src/pages/SettingsPage/components/SettingSwitchRow.tsx @@ -0,0 +1,85 @@ +import React from 'react'; + +interface SettingSwitchRowProps { + /** + * Base id used to derive the label/description ids referenced by + * aria-labelledby / aria-describedby + */ + id: string; + /** + * Row title + */ + label: string; + /** + * Row description + */ + description: string; + /** + * Whether the switch is on + */ + checked: boolean; + /** + * Whether the switch is disabled (loading / pending / unknown state) + */ + disabled: boolean; + /** + * Called when the switch is toggled + */ + onToggle: () => void; +} + +/** + * Reusable labeled toggle switch row used by the System settings card + */ +const SettingSwitchRow: React.FC = ({ + id, + label, + description, + checked, + disabled, + onToggle, +}) => { + const labelId = `${id}-label`; + const descId = `${id}-desc`; + + return ( +
+
+
+ {label} +
+
+ {description} +
+
+ + +
+ ); +}; + +export default SettingSwitchRow; diff --git a/frontend/src/pages/SettingsPage/components/SystemSettingsCard.tsx b/frontend/src/pages/SettingsPage/components/SystemSettingsCard.tsx index cc0f7b8be..0663525cf 100644 --- a/frontend/src/pages/SettingsPage/components/SystemSettingsCard.tsx +++ b/frontend/src/pages/SettingsPage/components/SystemSettingsCard.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'; import { Monitor } from 'lucide-react'; import { invoke } from '@tauri-apps/api/core'; import SettingsCard from './SettingsCard'; +import SettingSwitchRow from './SettingSwitchRow'; const SystemSettingsCard: React.FC = () => { // null = unknown / error reading state @@ -80,133 +81,44 @@ const SystemSettingsCard: React.FC = () => { loading || pendingStartMinimized || startMinimized === null; const startMinimizedChecked = startMinimized === true; + const autostartDescription = startMinimizedChecked + ? 'Automatically start PictoPy when you log in. The window starts minimized to the system tray.' + : 'Automatically start PictoPy when you log in. The main window opens directly on boot.'; + return ( -
-
-
- Launch at startup -
-
- Automatically start PictoPy when you log in. The window starts - minimized to the system tray. -
-
- - -
+ {isChecked && ( -
-
-
- Start minimized -
-
- When enabled, PictoPy starts silently in the system tray on boot. - When disabled, the main window opens on boot instead. -
-
- - -
+ )} -
-
-
- Close to tray -
-
- When enabled, closing the window hides the app to the system tray - instead of exiting. -
-
- - -
+
); }; From 5526097afe3378c3f24c5db2ae90c8c2f37da2ea Mon Sep 17 00:00:00 2001 From: g-k-s-03 Date: Sun, 16 Aug 2026 01:34:31 +0530 Subject: [PATCH 3/3] fix: default autostart description to tray text when unknown Base the Launch at startup description on startMinimized === false instead of the checked flag so a null (loading or unread) value falls back to the tray wording, matching the backend's default of true in get_start_minimized. Signed-off-by: g-k-s-03 --- .../pages/SettingsPage/components/SystemSettingsCard.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/SettingsPage/components/SystemSettingsCard.tsx b/frontend/src/pages/SettingsPage/components/SystemSettingsCard.tsx index 0663525cf..d8a2fb859 100644 --- a/frontend/src/pages/SettingsPage/components/SystemSettingsCard.tsx +++ b/frontend/src/pages/SettingsPage/components/SystemSettingsCard.tsx @@ -81,9 +81,10 @@ const SystemSettingsCard: React.FC = () => { loading || pendingStartMinimized || startMinimized === null; const startMinimizedChecked = startMinimized === true; - const autostartDescription = startMinimizedChecked - ? 'Automatically start PictoPy when you log in. The window starts minimized to the system tray.' - : 'Automatically start PictoPy when you log in. The main window opens directly on boot.'; + const autostartDescription = + startMinimized === false + ? 'Automatically start PictoPy when you log in. The main window opens directly on boot.' + : 'Automatically start PictoPy when you log in. The window starts minimized to the system tray.'; return (