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 <cj@cjpais.com>
This commit is contained in:
Gr3mIin 2025-10-10 04:47:51 +11:00 committed by GitHub
parent d602a3a8e2
commit aed3a75d54
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 81 additions and 3 deletions

View file

@ -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,

View file

@ -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,

View file

@ -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<String>) -> Result<(), String> {
let mut settings = settings::get_settings(&app);

View file

@ -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 (
<div className="max-w-3xl w-full mx-auto space-y-6">
<SettingsGroup title="Advanced">
<StartHidden descriptionMode="tooltip" grouped={true} />
<AutostartToggle descriptionMode="tooltip" grouped={true} />
<ShowOverlay descriptionMode="tooltip" grouped={true} />
<TranslateToEnglish descriptionMode="tooltip" grouped={true} />
<ModelUnloadTimeoutSetting descriptionMode="tooltip" grouped={true} />

View file

@ -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<AutostartToggleProps> = React.memo(
({ descriptionMode = "tooltip", grouped = false }) => {
const { getSetting, updateSetting, isUpdating } = useSettings();
const autostartEnabled = getSetting("autostart_enabled") ?? false;
return (
<ToggleSwitch
checked={autostartEnabled}
onChange={(enabled) => 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}
/>
);
},
);

View file

@ -19,3 +19,4 @@ export { AppDataDirectory } from "./AppDataDirectory";
export { ModelUnloadTimeoutSetting } from "./ModelUnloadTimeout";
export { StartHidden } from "./StartHidden";
export { HistoryLimit } from "./HistoryLimit";
export { AutostartToggle } from "./AutostartToggle";

View file

@ -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(),

View file

@ -37,6 +37,7 @@ const DEFAULT_SETTINGS: Partial<Settings> = {
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<SettingsStore>()(
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;