Skip to content

Commit 77920b6

Browse files
author
Mohammod Al Amin Ashik
committed
feat: Add auto-start and system tray functionality (WIP)
- Add tauri-plugin-autostart for launch at startup - Implement close-to-tray behavior with window event handler - Add startup settings commands (get/update) - Create Switch UI component - Add Settings UI for auto-launch, start minimized, and close-to-tray - Support --hidden flag for background startup Note: Has compilation error in mcpmux-core to be fixed after merge
1 parent 9dfb59f commit 77920b6

12 files changed

Lines changed: 975 additions & 17 deletions

File tree

Cargo.lock

Lines changed: 61 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/desktop/src-tauri/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ tauri-plugin-opener = "2"
2020
tauri-plugin-single-instance = "2"
2121
tauri-plugin-deep-link = "2"
2222
tauri-plugin-updater = "2"
23+
tauri-plugin-autostart = "2"
2324
serde.workspace = true
2425
serde_json.workspace = true
2526
tokio.workspace = true

apps/desktop/src-tauri/src/commands/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub mod server;
1616
pub mod server_discovery;
1717
pub mod server_feature;
1818
pub mod server_manager;
19+
pub mod settings;
1920
pub mod space;
2021

2122
// Re-export commands for convenience
@@ -31,4 +32,5 @@ pub use server::*;
3132
pub use server_discovery::*;
3233
pub use server_feature::*;
3334
pub use server_manager::*;
35+
pub use settings::*;
3436
pub use space::*;
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
//! Settings commands for auto-start and system tray behavior
2+
3+
use serde::{Deserialize, Serialize};
4+
use tauri::State;
5+
use tauri_plugin_autostart::AutoLaunchManager;
6+
use tracing::{debug, error, info};
7+
8+
use crate::state::AppState;
9+
10+
/// Startup and system tray settings
11+
#[derive(Debug, Clone, Serialize, Deserialize)]
12+
#[serde(rename_all = "camelCase")]
13+
pub struct StartupSettings {
14+
/// Whether to launch the app at system startup
15+
pub auto_launch: bool,
16+
/// Whether to start minimized to tray
17+
pub start_minimized: bool,
18+
/// Whether to minimize to tray instead of closing
19+
pub close_to_tray: bool,
20+
}
21+
22+
impl Default for StartupSettings {
23+
fn default() -> Self {
24+
Self {
25+
auto_launch: false,
26+
start_minimized: false,
27+
close_to_tray: true, // Default to close-to-tray behavior
28+
}
29+
}
30+
}
31+
32+
/// Get current startup settings
33+
#[tauri::command]
34+
pub async fn get_startup_settings(
35+
app_state: State<'_, AppState>,
36+
manager: State<'_, AutoLaunchManager>,
37+
) -> Result<StartupSettings, String> {
38+
debug!("[Settings] Getting startup settings");
39+
40+
let settings_repo = &app_state.settings_repository;
41+
42+
// Get auto-launch status from the OS
43+
let auto_launch = manager
44+
.is_enabled()
45+
.await
46+
.map_err(|e| format!("Failed to check auto-launch status: {}", e))?;
47+
48+
// Get other settings from database
49+
let start_minimized = settings_repo
50+
.get("startup.start_minimized")
51+
.await
52+
.map_err(|e| format!("Failed to get start_minimized setting: {}", e))?
53+
.map(|v| v == "true")
54+
.unwrap_or(false);
55+
56+
let close_to_tray = settings_repo
57+
.get("ui.close_to_tray")
58+
.await
59+
.map_err(|e| format!("Failed to get close_to_tray setting: {}", e))?
60+
.map(|v| v == "true")
61+
.unwrap_or(true); // Default to true
62+
63+
Ok(StartupSettings {
64+
auto_launch,
65+
start_minimized,
66+
close_to_tray,
67+
})
68+
}
69+
70+
/// Update startup settings
71+
#[tauri::command]
72+
pub async fn update_startup_settings(
73+
settings: StartupSettings,
74+
app_state: State<'_, AppState>,
75+
manager: State<'_, AutoLaunchManager>,
76+
) -> Result<(), String> {
77+
info!("[Settings] Updating startup settings: {:?}", settings);
78+
79+
let settings_repo = &app_state.settings_repository;
80+
81+
// Update auto-launch in OS
82+
if settings.auto_launch {
83+
manager
84+
.enable()
85+
.await
86+
.map_err(|e| format!("Failed to enable auto-launch: {}", e))?;
87+
info!("[Settings] Auto-launch enabled");
88+
} else {
89+
manager
90+
.disable()
91+
.await
92+
.map_err(|e| format!("Failed to disable auto-launch: {}", e))?;
93+
info!("[Settings] Auto-launch disabled");
94+
}
95+
96+
// Update other settings in database
97+
settings_repo
98+
.set(
99+
"startup.start_minimized",
100+
&settings.start_minimized.to_string(),
101+
)
102+
.await
103+
.map_err(|e| format!("Failed to save start_minimized setting: {}", e))?;
104+
105+
settings_repo
106+
.set("ui.close_to_tray", &settings.close_to_tray.to_string())
107+
.await
108+
.map_err(|e| format!("Failed to save close_to_tray setting: {}", e))?;
109+
110+
info!("[Settings] Startup settings updated successfully");
111+
Ok(())
112+
}
113+
114+
/// Check if app should start hidden (for auto-launch with --hidden flag)
115+
pub fn should_start_hidden() -> bool {
116+
let args: Vec<String> = std::env::args().collect();
117+
args.contains(&"--hidden".to_string())
118+
}

apps/desktop/src-tauri/src/lib.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,10 @@ pub fn run() {
189189
tauri::Builder::default()
190190
.plugin(tauri_plugin_opener::init())
191191
.plugin(tauri_plugin_deep_link::init())
192+
.plugin(tauri_plugin_autostart::init(
193+
tauri_plugin_autostart::MacosLauncher::LaunchAgent,
194+
Some(vec!["--hidden"]), // Start minimized to tray
195+
))
192196
.plugin(tauri_plugin_single_instance::init(|app, args, cwd| {
193197
// This callback is called when a second instance is launched
194198
info!("Second instance detected, focusing existing window");
@@ -517,6 +521,53 @@ pub fn run() {
517521
// Setup system tray
518522
tray::setup_tray(app.handle())?;
519523

524+
// Setup window close event handler for close-to-tray behavior
525+
if let Some(main_window) = app.get_webview_window("main") {
526+
let app_handle = app.handle().clone();
527+
let settings_repo = app_state.settings_repository.clone();
528+
529+
main_window.on_window_event(move |event| {
530+
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
531+
// Check if close-to-tray is enabled
532+
let app_handle_clone = app_handle.clone();
533+
let settings_clone = settings_repo.clone();
534+
535+
tauri::async_runtime::spawn(async move {
536+
match settings_clone.get("ui.close_to_tray").await {
537+
Ok(Some(value)) if value == "true" => {
538+
// Close to tray - hide window instead of closing
539+
info!("[Window] Close requested, hiding to tray");
540+
if let Some(window) = app_handle_clone.get_webview_window("main") {
541+
let _ = window.hide();
542+
}
543+
}
544+
Ok(Some(value)) if value == "false" => {
545+
// Actually close the app
546+
info!("[Window] Close requested, exiting app");
547+
app_handle_clone.exit(0);
548+
}
549+
_ => {
550+
// Default behavior: close to tray
551+
info!("[Window] Close requested (default), hiding to tray");
552+
if let Some(window) = app_handle_clone.get_webview_window("main") {
553+
let _ = window.hide();
554+
}
555+
}
556+
}
557+
});
558+
559+
// Always prevent default close to handle it asynchronously
560+
api.prevent_close();
561+
}
562+
});
563+
564+
// Check if app should start hidden (auto-launch with --hidden flag)
565+
if commands::should_start_hidden() {
566+
info!("[Window] Starting hidden (--hidden flag present)");
567+
let _ = main_window.hide();
568+
}
569+
}
570+
520571
// Register deep link handler for when app receives URLs
521572
#[cfg(desktop)]
522573
{
@@ -644,6 +695,9 @@ pub fn run() {
644695
// App log commands
645696
get_logs_path,
646697
open_logs_folder,
698+
// Startup settings commands
699+
commands::get_startup_settings,
700+
commands::update_startup_settings,
647701
])
648702
.run(tauri::generate_context!())
649703
.expect("error while running McpMux application");

0 commit comments

Comments
 (0)