From dab9a807a52d5cbcdf04022e7d440d59f612a75c Mon Sep 17 00:00:00 2001 From: Leafd Date: Fri, 10 Oct 2025 15:30:11 -0400 Subject: [PATCH] feat: add autostart functionality --- package.json | 1 + src-tauri/Cargo.lock | 82 ++++++++++++++++++++++++--- src-tauri/Cargo.toml | 1 + src-tauri/capabilities/default.json | 5 +- src-tauri/capabilities/desktop.json | 5 +- src-tauri/src/lib.rs | 29 +++++++++- src-tauri/src/preferences.rs | 88 +++++++++++++++++++++++++++++ src/views/Settings.vue | 33 ++++++++++- 8 files changed, 229 insertions(+), 15 deletions(-) create mode 100644 src-tauri/src/preferences.rs diff --git a/package.json b/package.json index e0e4bd6..7c5dff4 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dependencies": { "@sentry/vue": "^10.18.0", "@tauri-apps/api": "^2", + "@tauri-apps/plugin-autostart": "^2", "@tauri-apps/plugin-deep-link": "^2.4.3", "@tauri-apps/plugin-opener": "^2", "@tauri-apps/plugin-process": "~2", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index f02a857..1c8dcfb 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -240,6 +240,17 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "auto-launch" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f012b8cc0c850f34117ec8252a44418f2e34a2cf501de89e29b241ae5f79471" +dependencies = [ + "dirs 4.0.0", + "thiserror 1.0.69", + "winreg 0.10.1", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -833,6 +844,7 @@ dependencies = [ "sqlx", "tauri", "tauri-build", + "tauri-plugin-autostart", "tauri-plugin-deep-link", "tauri-plugin-opener", "tauri-plugin-process", @@ -855,13 +867,33 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys 0.3.7", +] + [[package]] name = "dirs" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "dirs-sys", + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users 0.4.6", + "winapi", ] [[package]] @@ -872,7 +904,7 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users", + "redox_users 0.5.2", "windows-sys 0.61.1", ] @@ -1012,7 +1044,7 @@ dependencies = [ "rustc_version", "toml 0.9.7", "vswhom", - "winreg", + "winreg 0.55.0", ] [[package]] @@ -3633,6 +3665,17 @@ dependencies = [ "bitflags 2.9.4", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 1.0.69", +] + [[package]] name = "redox_users" version = "0.5.2" @@ -4740,7 +4783,7 @@ dependencies = [ "anyhow", "bytes", "cookie", - "dirs", + "dirs 6.0.0", "dunce", "embed_plist", "getrandom 0.3.3", @@ -4791,7 +4834,7 @@ checksum = "9c432ccc9ff661803dab74c6cd78de11026a578a9307610bbc39d3c55be7943f" dependencies = [ "anyhow", "cargo_toml", - "dirs", + "dirs 6.0.0", "glob", "heck 0.5.0", "json-patch", @@ -4863,6 +4906,20 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tauri-plugin-autostart" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "062cdcd483d5e3148c9a64dabf8c574e239e2aa1193cf208d95cf89a676f87a5" +dependencies = [ + "auto-launch", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.17", +] + [[package]] name = "tauri-plugin-deep-link" version = "2.4.3" @@ -4938,7 +4995,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27cbc31740f4d507712550694749572ec0e43bdd66992db7599b89fbfd6b167b" dependencies = [ "base64 0.22.1", - "dirs", + "dirs 6.0.0", "flate2", "futures-util", "http", @@ -5447,7 +5504,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0d92153331e7d02ec09137538996a7786fe679c629c279e82a6be762b7e6fe2" dependencies = [ "crossbeam-channel", - "dirs", + "dirs 6.0.0", "libappindicator", "muda", "objc2 0.6.3", @@ -6469,6 +6526,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + [[package]] name = "winreg" version = "0.55.0" @@ -6501,7 +6567,7 @@ dependencies = [ "block2 0.6.2", "cookie", "crossbeam-channel", - "dirs", + "dirs 6.0.0", "dpi", "dunce", "gdkx11", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 133abe7..977f4bd 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -22,6 +22,7 @@ tauri = { version = "2", features = ["tray-icon"] } tauri-plugin-opener = "2" tauri-plugin-deep-link = "2" tauri-plugin-single-instance = "2" +tauri-plugin-autostart = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" open = "5" diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 76a1ace..a8f0bb0 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -13,6 +13,9 @@ "opener:default", "deep-link:default", "updater:default", - "process:default" + "process:default", + "autostart:allow-enable", + "autostart:allow-disable", + "autostart:allow-is-enabled" ] } diff --git a/src-tauri/capabilities/desktop.json b/src-tauri/capabilities/desktop.json index 2784d0e..9e62e1c 100644 --- a/src-tauri/capabilities/desktop.json +++ b/src-tauri/capabilities/desktop.json @@ -27,6 +27,9 @@ "opener:default", "deep-link:default", "updater:default", - "process:default" + "process:default", + "autostart:allow-enable", + "autostart:allow-disable", + "autostart:allow-is-enabled" ] } \ No newline at end of file diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 5794073..c55a73b 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -11,6 +11,7 @@ mod config; mod database; mod db_commands; mod discord_rpc; +mod preferences; mod projects; mod session; mod setup; @@ -69,14 +70,12 @@ pub fn run() { .plugin(tauri_plugin_single_instance::init(|app, args, cwd| { push_log("info", "backend", format!("Single instance detected. Args: {:?}, CWD: {}", args, cwd)); - // Show the existing window if let Some(window) = app.get_webview_window("main") { let _ = window.show(); let _ = window.set_focus(); push_log("info", "backend", "Brought existing window to front".to_string()); } - // Process any deep links from the new instance attempt for arg in args { if arg.starts_with("hackatime://") { push_log("info", "backend", format!("Processing deep link from second instance: {}", arg)); @@ -84,6 +83,7 @@ pub fn run() { } } })) + .plugin(tauri_plugin_autostart::init(tauri_plugin_autostart::MacosLauncher::LaunchAgent, Some(vec!["--minimized"]))) .plugin(tauri_plugin_process::init()) .plugin(tauri_plugin_updater::Builder::new().build()) .plugin(tauri_plugin_opener::init()) @@ -128,6 +128,10 @@ pub fn run() { auth::load_auth_state, auth::clear_auth_state, + preferences::get_preferences, + preferences::set_autostart_enabled, + preferences::get_autostart_enabled, + setup::setup_hackatime_macos_linux, setup::setup_hackatime_windows, setup::test_hackatime_heartbeat, @@ -260,6 +264,27 @@ pub fn run() { Err(e) => push_log("warn", "backend", format!("Discord RPC auto-connect failed (this is optional): {}", e)), } }); + + use tauri_plugin_autostart::ManagerExt; + let autolaunch_manager = app.autolaunch(); + match preferences::load_preferences() { + Ok(prefs) => { + if prefs.autostart_enabled { + match autolaunch_manager.enable() { + Ok(_) => push_log("info", "backend", "Autostart enabled on app startup".to_string()), + Err(e) => push_log("error", "backend", format!("Failed to enable autostart: {}", e)), + } + } else { + match autolaunch_manager.disable() { + Ok(_) => push_log("info", "backend", "Autostart disabled on app startup".to_string()), + Err(e) => push_log("error", "backend", format!("Failed to disable autostart: {}", e)), + } + } + } + Err(e) => { + push_log("warn", "backend", format!("Failed to load preferences for autostart: {}", e)); + } + } #[cfg(any(target_os = "linux", target_os = "windows"))] diff --git a/src-tauri/src/preferences.rs b/src-tauri/src/preferences.rs new file mode 100644 index 0000000..ce22c4c --- /dev/null +++ b/src-tauri/src/preferences.rs @@ -0,0 +1,88 @@ +use serde::{Deserialize, Serialize}; +use std::fs; +use std::path::PathBuf; +use tauri::AppHandle; +use tauri_plugin_autostart::ManagerExt; +use crate::database::get_hackatime_config_dir; +use crate::push_log; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Preferences { + pub autostart_enabled: bool, +} + +impl Default for Preferences { + fn default() -> Self { + Self { + autostart_enabled: false, + } + } +} + +fn get_preferences_path() -> Result { + let config_dir = get_hackatime_config_dir()?; + Ok(config_dir.join("preferences.json")) +} + +pub fn load_preferences() -> Result { + let path = get_preferences_path()?; + + if !path.exists() { + push_log("info", "backend", "No preferences file found, using defaults".to_string()); + return Ok(Preferences::default()); + } + + let contents = fs::read_to_string(&path) + .map_err(|e| format!("Failed to read preferences file: {}", e))?; + + let preferences: Preferences = serde_json::from_str(&contents) + .map_err(|e| format!("Failed to parse preferences: {}", e))?; + + push_log("info", "backend", "Loaded preferences successfully".to_string()); + Ok(preferences) +} + +pub fn save_preferences(preferences: &Preferences) -> Result<(), String> { + let path = get_preferences_path()?; + + let contents = serde_json::to_string_pretty(preferences) + .map_err(|e| format!("Failed to serialize preferences: {}", e))?; + + fs::write(&path, contents) + .map_err(|e| format!("Failed to write preferences file: {}", e))?; + + push_log("info", "backend", "Saved preferences successfully".to_string()); + Ok(()) +} + +#[tauri::command] +pub fn get_preferences() -> Result { + load_preferences() +} + +#[tauri::command] +pub fn set_autostart_enabled(app: AppHandle, enabled: bool) -> Result<(), String> { + let mut preferences = load_preferences().unwrap_or_default(); + preferences.autostart_enabled = enabled; + save_preferences(&preferences)?; + + let autolaunch_manager = app.autolaunch(); + if enabled { + autolaunch_manager.enable() + .map_err(|e| format!("Failed to enable autostart: {}", e))?; + push_log("info", "backend", "Autostart enabled".to_string()); + } else { + autolaunch_manager.disable() + .map_err(|e| format!("Failed to disable autostart: {}", e))?; + push_log("info", "backend", "Autostart disabled".to_string()); + } + + Ok(()) +} + +#[tauri::command] +pub fn get_autostart_enabled() -> Result { + let preferences = load_preferences().unwrap_or_default(); + Ok(preferences.autostart_enabled) +} + diff --git a/src/views/Settings.vue b/src/views/Settings.vue index 5d41c81..5b6d7c8 100644 --- a/src/views/Settings.vue +++ b/src/views/Settings.vue @@ -25,9 +25,9 @@

Auto-start

Start with system

-