diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 1e988a8d9..28c573f23 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -4957,6 +4957,15 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +[[package]] +name = "planif" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7502ee5af341c06aa00593d655d09e4b7126f0e886b80745c218d6dc674e8b21" +dependencies = [ + "windows 0.48.0", +] + [[package]] name = "plist" version = "1.7.0" @@ -6781,6 +6790,7 @@ dependencies = [ "open", "openssl", "phraze", + "planif", "rand 0.8.5", "regex", "reqwest", @@ -6812,6 +6822,7 @@ dependencies = [ "thiserror 1.0.69", "tokio", "tokio-util 0.7.13", + "whoami", "winreg 0.52.0", "xz2", "zip", @@ -8464,6 +8475,12 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" version = "0.2.99" @@ -8665,6 +8682,17 @@ dependencies = [ "rustix", ] +[[package]] +name = "whoami" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +dependencies = [ + "redox_syscall", + "wasite", + "web-sys", +] + [[package]] name = "widestring" version = "1.1.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 466cb128b..fafbe2b2f 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -92,6 +92,8 @@ tauri-plugin-process = "2" openssl = { version = "0.10", features = ["vendored"] } [target.'cfg(windows)'.dependencies] +planif = "1.0.0" +whoami = "1.5.2" winreg = "0.52.0" # needed for keymanager. TODO: Find a way of creating a keymanager without bundling sqlite diff --git a/src-tauri/src/auto_launcher.rs b/src-tauri/src/auto_launcher.rs index e43185327..91543e422 100644 --- a/src-tauri/src/auto_launcher.rs +++ b/src-tauri/src/auto_launcher.rs @@ -26,19 +26,24 @@ use anyhow::anyhow; use auto_launch::{AutoLaunch, AutoLaunchBuilder}; use dunce::canonicalize; use log::{info, warn}; +#[cfg(target_os = "windows")] +use planif::{ + enums::TaskCreationFlags, + schedule::TaskScheduler, + schedule_builder::{Action, ScheduleBuilder}, + settings::{LogonType, PrincipalSettings, RunLevel, Settings}, +}; use tauri::utils::platform::current_exe; use tokio::sync::RwLock; +#[cfg(target_os = "windows")] +use whoami::username; + +use crate::utils::platform_utils::{CurrentOperatingSystem, PlatformUtils}; const LOG_TARGET: &str = "tari::universe::auto_launcher"; static INSTANCE: LazyLock = LazyLock::new(AutoLauncher::new); -pub enum CurrentOperatingSystem { - Windows, - Linux, - MacOS, -} - pub struct AutoLauncher { auto_launcher: RwLock>, } @@ -50,22 +55,10 @@ impl AutoLauncher { } } - fn detect_current_os() -> CurrentOperatingSystem { - if cfg!(target_os = "windows") { - CurrentOperatingSystem::Windows - } else if cfg!(target_os = "linux") { - CurrentOperatingSystem::Linux - } else if cfg!(target_os = "macos") { - CurrentOperatingSystem::MacOS - } else { - panic!("Unsupported OS"); - } - } - fn build_auto_launcher(app_name: &str, app_path: &str) -> Result { info!(target: LOG_TARGET, "Building auto-launcher with app_name: {} and app_path: {}", app_name, app_path); - match AutoLauncher::detect_current_os() { + match PlatformUtils::detect_current_os() { CurrentOperatingSystem::Windows => { return AutoLaunchBuilder::new() .set_app_name(app_name) @@ -93,7 +86,8 @@ impl AutoLauncher { } } - fn toggle_auto_launcher( + async fn toggle_auto_launcher( + &self, auto_launcher: &AutoLaunch, config_is_auto_launcher_enabled: bool, ) -> Result<(), anyhow::Error> { @@ -101,13 +95,20 @@ impl AutoLauncher { if config_is_auto_launcher_enabled && !is_auto_launcher_enabled { info!(target: LOG_TARGET, "Enabling auto-launcher"); - match AutoLauncher::detect_current_os() { + match PlatformUtils::detect_current_os() { CurrentOperatingSystem::MacOS => { // This for some reason fixes the issue where macOS starts two instances of the app // when auto-launcher is enabled and when during shutdown user selects to reopen the apps after restart auto_launcher.disable()?; auto_launcher.enable()?; } + CurrentOperatingSystem::Windows => { + auto_launcher.enable()?; + // To startup application as admin on windows, we need to create a task scheduler + #[cfg(target_os = "windows")] + self.toggle_windows_admin_auto_launcher(is_auto_launcher_enabled) + .await?; + } _ => { auto_launcher.enable()?; } @@ -117,12 +118,93 @@ impl AutoLauncher { if !config_is_auto_launcher_enabled && is_auto_launcher_enabled { info!(target: LOG_TARGET, "Disabling auto-launcher"); - auto_launcher.disable()?; + match PlatformUtils::detect_current_os() { + CurrentOperatingSystem::Windows => { + #[cfg(target_os = "windows")] + self.toggle_windows_admin_auto_launcher(is_auto_launcher_enabled) + .await?; + auto_launcher.disable()?; + } + _ => { + auto_launcher.disable()?; + } + } } Ok(()) } + #[cfg(target_os = "windows")] + async fn toggle_windows_admin_auto_launcher( + &self, + config_is_auto_launcher_enabled: bool, + ) -> Result<(), anyhow::Error> { + if config_is_auto_launcher_enabled { + info!(target: LOG_TARGET, "Enabling admin auto-launcher"); + self.create_task_scheduler_for_admin_startup(true) + .await + .map_err(|e| anyhow!("Failed to create task scheduler for admin startup: {}", e))?; + }; + + if !config_is_auto_launcher_enabled { + info!(target: LOG_TARGET, "Disabling admin auto-launcher"); + self.create_task_scheduler_for_admin_startup(false) + .await + .map_err(|e| anyhow!("Failed to create task scheduler for admin startup: {}", e))?; + }; + + Ok(()) + } + + #[cfg(target_os = "windows")] + pub async fn create_task_scheduler_for_admin_startup( + &self, + is_triggered: bool, + ) -> Result<(), Box> { + let task_scheduler = TaskScheduler::new()?; + let com_runtime = task_scheduler.get_com(); + let schedule_builder = ScheduleBuilder::new(&com_runtime)?; + + let app_exe = current_exe()?; + let app_exe = canonicalize(&app_exe)?; + + let app_path = app_exe + .as_os_str() + .to_str() + .ok_or(anyhow!("Failed to convert path to string"))? + .to_string(); + + schedule_builder + .create_logon() + .author("Tari Universe")? + .trigger("startup_trigger", is_triggered)? + .action(Action::new("startup_action", &app_path, "", ""))? + .principal(PrincipalSettings { + display_name: "Tari Universe".to_string(), + group_id: None, + user_id: Some(username()), + id: "Tari universe principal".to_string(), + logon_type: LogonType::InteractiveToken, + run_level: RunLevel::Highest, + })? + .settings(Settings { + stop_if_going_on_batteries: Some(false), + start_when_available: Some(true), + run_only_if_network_available: Some(false), + run_only_if_idle: Some(false), + enabled: Some(true), + disallow_start_if_on_batteries: Some(false), + ..Default::default() + })? + .build()? + .register( + "Tari Universe startup", + TaskCreationFlags::CreateOrUpdate as i32, + )?; + + Ok(()) + } + pub async fn initialize_auto_launcher( &self, is_auto_launcher_enabled: bool, @@ -145,7 +227,8 @@ impl AutoLauncher { info!(target: LOG_TARGET, "Building auto-launcher with app_name: {} and app_path: {}", app_name, app_path); let auto_launcher = AutoLauncher::build_auto_launcher(app_name, &app_path)?; - AutoLauncher::toggle_auto_launcher(&auto_launcher, is_auto_launcher_enabled)?; + self.toggle_auto_launcher(&auto_launcher, is_auto_launcher_enabled) + .await?; let _ = &self.auto_launcher.write().await.replace(auto_launcher); @@ -168,7 +251,8 @@ impl AutoLauncher { let auto_launcher_ref = auto_launcher.as_ref(); match auto_launcher_ref { Some(auto_launcher) => { - AutoLauncher::toggle_auto_launcher(auto_launcher, is_auto_launcher_enabled)?; + self.toggle_auto_launcher(auto_launcher, is_auto_launcher_enabled) + .await?; } None => { warn!(target: LOG_TARGET, "Could not get auto-launcher reference");