From aed3a75d549652f5e1103c08872e6b3f154a12e9 Mon Sep 17 00:00:00 2001 From: Gr3mIin <33955748+Gr3mIin@users.noreply.github.com> Date: Fri, 10 Oct 2025 04:47:51 +1100 Subject: [PATCH] feat(settings): add autostart toggle in advanced settings (#177) * feat(settings): add autostart toggle in advanced settings Enable users to configure autostart behavior through a new toggle in the advanced settings UI. The backend now checks user preferences on startup to enable or disable autostart accordingly, with the setting persisted and manageable via a new Tauri command. This provides better control over app initialization without affecting existing functionality. * immediate toggle effect? --------- Co-authored-by: CJ Pais --- src-tauri/src/lib.rs | 14 +++++++--- src-tauri/src/settings.rs | 7 +++++ src-tauri/src/shortcut.rs | 27 +++++++++++++++++++ src/components/settings/AdvancedSettings.tsx | 2 ++ src/components/settings/AutostartToggle.tsx | 28 ++++++++++++++++++++ src/components/settings/index.ts | 1 + src/lib/types.ts | 1 + src/stores/settingsStore.ts | 4 +++ 8 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 src/components/settings/AutostartToggle.tsx diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index c307001..441782d 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -137,10 +137,17 @@ pub fn run() { // Initialize tray menu with idle state utils::update_tray_menu(&app.handle(), &utils::TrayIconState::Idle); - // Get the autostart manager + // Get the autostart manager and configure based on user setting let autostart_manager = app.autolaunch(); - // Enable autostart - let _ = autostart_manager.enable(); + let settings = settings::get_settings(&app.handle()); + + if settings.autostart_enabled { + // Enable autostart if user has opted in + let _ = autostart_manager.enable(); + } else { + // Disable autostart if user has opted out + let _ = autostart_manager.disable(); + } // Window is configured to start hidden to avoid flicker. // If user didn't choose Start Hidden, show it now. @@ -205,6 +212,7 @@ pub fn run() { shortcut::change_ptt_setting, shortcut::change_audio_feedback_setting, shortcut::change_start_hidden_setting, + shortcut::change_autostart_setting, shortcut::change_translate_to_english_setting, shortcut::change_selected_language_setting, shortcut::change_overlay_position_setting, diff --git a/src-tauri/src/settings.rs b/src-tauri/src/settings.rs index 9599ef7..8d59550 100644 --- a/src-tauri/src/settings.rs +++ b/src-tauri/src/settings.rs @@ -88,6 +88,8 @@ pub struct AppSettings { pub audio_feedback: bool, #[serde(default = "default_start_hidden")] pub start_hidden: bool, + #[serde(default = "default_autostart_enabled")] + pub autostart_enabled: bool, #[serde(default = "default_model")] pub selected_model: String, #[serde(default = "default_always_on_microphone")] @@ -132,6 +134,10 @@ fn default_start_hidden() -> bool { false } +fn default_autostart_enabled() -> bool { + false +} + fn default_selected_language() -> String { "auto".to_string() } @@ -181,6 +187,7 @@ pub fn get_default_settings() -> AppSettings { push_to_talk: true, audio_feedback: false, start_hidden: default_start_hidden(), + autostart_enabled: default_autostart_enabled(), selected_model: "".to_string(), always_on_microphone: false, selected_microphone: None, diff --git a/src-tauri/src/shortcut.rs b/src-tauri/src/shortcut.rs index cda0a4e..8b84ba8 100644 --- a/src-tauri/src/shortcut.rs +++ b/src-tauri/src/shortcut.rs @@ -1,5 +1,6 @@ use serde::Serialize; use tauri::{App, AppHandle, Emitter, Manager}; +use tauri_plugin_autostart::ManagerExt; use tauri_plugin_global_shortcut::GlobalShortcutExt; use tauri_plugin_global_shortcut::{Shortcut, ShortcutState}; @@ -191,6 +192,32 @@ pub fn change_start_hidden_setting(app: AppHandle, enabled: bool) -> Result<(), Ok(()) } +#[tauri::command] +pub fn change_autostart_setting(app: AppHandle, enabled: bool) -> Result<(), String> { + let mut settings = settings::get_settings(&app); + settings.autostart_enabled = enabled; + settings::write_settings(&app, settings); + + // Apply the autostart setting immediately + let autostart_manager = app.autolaunch(); + if enabled { + let _ = autostart_manager.enable(); + } else { + let _ = autostart_manager.disable(); + } + + // Notify frontend + let _ = app.emit( + "settings-changed", + serde_json::json!({ + "setting": "autostart_enabled", + "value": enabled + }), + ); + + Ok(()) +} + #[tauri::command] pub fn update_custom_words(app: AppHandle, words: Vec) -> Result<(), String> { let mut settings = settings::get_settings(&app); diff --git a/src/components/settings/AdvancedSettings.tsx b/src/components/settings/AdvancedSettings.tsx index 8795e7e..faa9f16 100644 --- a/src/components/settings/AdvancedSettings.tsx +++ b/src/components/settings/AdvancedSettings.tsx @@ -6,12 +6,14 @@ import { CustomWords } from "./CustomWords"; import { AlwaysOnMicrophone } from "./AlwaysOnMicrophone"; import { SettingsGroup } from "../ui/SettingsGroup"; import { StartHidden } from "./StartHidden"; +import { AutostartToggle } from "./AutostartToggle"; export const AdvancedSettings: React.FC = () => { return (
+ diff --git a/src/components/settings/AutostartToggle.tsx b/src/components/settings/AutostartToggle.tsx new file mode 100644 index 0000000..c71d962 --- /dev/null +++ b/src/components/settings/AutostartToggle.tsx @@ -0,0 +1,28 @@ +import React from "react"; +import { ToggleSwitch } from "../ui/ToggleSwitch"; +import { useSettings } from "../../hooks/useSettings"; + +interface AutostartToggleProps { + descriptionMode?: "inline" | "tooltip"; + grouped?: boolean; +} + +export const AutostartToggle: React.FC = React.memo( + ({ descriptionMode = "tooltip", grouped = false }) => { + const { getSetting, updateSetting, isUpdating } = useSettings(); + + const autostartEnabled = getSetting("autostart_enabled") ?? false; + + return ( + updateSetting("autostart_enabled", enabled)} + isUpdating={isUpdating("autostart_enabled")} + label="Launch on Startup" + description="Automatically start Handy when you log in to your computer." + descriptionMode={descriptionMode} + grouped={grouped} + /> + ); + }, +); diff --git a/src/components/settings/index.ts b/src/components/settings/index.ts index 3c8af85..dc11e15 100644 --- a/src/components/settings/index.ts +++ b/src/components/settings/index.ts @@ -19,3 +19,4 @@ export { AppDataDirectory } from "./AppDataDirectory"; export { ModelUnloadTimeoutSetting } from "./ModelUnloadTimeout"; export { StartHidden } from "./StartHidden"; export { HistoryLimit } from "./HistoryLimit"; +export { AutostartToggle } from "./AutostartToggle"; diff --git a/src/lib/types.ts b/src/lib/types.ts index 2a95f06..70a4424 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -42,6 +42,7 @@ export const SettingsSchema = z.object({ push_to_talk: z.boolean(), audio_feedback: z.boolean(), start_hidden: z.boolean().optional().default(false), + autostart_enabled: z.boolean().optional().default(false), selected_model: z.string(), always_on_microphone: z.boolean(), selected_microphone: z.string().nullable().optional(), diff --git a/src/stores/settingsStore.ts b/src/stores/settingsStore.ts index 01905e2..1e35265 100644 --- a/src/stores/settingsStore.ts +++ b/src/stores/settingsStore.ts @@ -37,6 +37,7 @@ const DEFAULT_SETTINGS: Partial = { always_on_microphone: false, audio_feedback: true, start_hidden: false, + autostart_enabled: false, push_to_talk: false, selected_microphone: "Default", selected_output_device: "Default", @@ -181,6 +182,9 @@ export const useSettingsStore = create()( case "start_hidden": await invoke("change_start_hidden_setting", { enabled: value }); break; + case "autostart_enabled": + await invoke("change_autostart_setting", { enabled: value }); + break; case "push_to_talk": await invoke("change_ptt_setting", { enabled: value }); break;