// Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] mod elevate; use std::collections::BTreeMap; use easytier::{ common::config::{ConfigLoader, FileLoggerConfig, LoggingConfigBuilder, TomlConfigLoader}, instance_manager::NetworkInstanceManager, launcher::{ConfigSource, NetworkConfig, NetworkInstanceRunningInfo}, utils::{self, NewFilterSender}, }; use tauri::Manager as _; pub const AUTOSTART_ARG: &str = "--autostart"; #[cfg(not(target_os = "android"))] use tauri::tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}; static INSTANCE_MANAGER: once_cell::sync::Lazy = once_cell::sync::Lazy::new(NetworkInstanceManager::new); static mut LOGGER_LEVEL_SENDER: once_cell::sync::Lazy> = once_cell::sync::Lazy::new(Default::default); #[tauri::command] fn easytier_version() -> Result { Ok(easytier::VERSION.to_string()) } #[tauri::command] fn set_dock_visibility(app: tauri::AppHandle, visible: bool) -> Result<(), String> { #[cfg(target_os = "macos")] { use tauri::ActivationPolicy; app.set_activation_policy(if visible { ActivationPolicy::Regular } else { ActivationPolicy::Accessory }) .map_err(|e| e.to_string())?; } #[cfg(not(target_os = "macos"))] let _ = (app, visible); Ok(()) } #[tauri::command] fn is_autostart() -> Result { let args: Vec = std::env::args().collect(); println!("{:?}", args); Ok(args.contains(&AUTOSTART_ARG.to_owned())) } // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command #[tauri::command] fn parse_network_config(cfg: NetworkConfig) -> Result { let toml = cfg.gen_config().map_err(|e| e.to_string())?; Ok(toml.dump()) } #[tauri::command] fn generate_network_config(toml_config: String) -> Result { let config = TomlConfigLoader::new_from_str(&toml_config).map_err(|e| e.to_string())?; let cfg = NetworkConfig::new_from_config(&config).map_err(|e| e.to_string())?; Ok(cfg) } #[tauri::command] fn run_network_instance(cfg: NetworkConfig) -> Result<(), String> { let instance_id = cfg.instance_id().to_string(); let cfg = cfg.gen_config().map_err(|e| e.to_string())?; INSTANCE_MANAGER .run_network_instance(cfg, ConfigSource::GUI) .map_err(|e| e.to_string())?; println!("instance {} started", instance_id); Ok(()) } #[tauri::command] fn retain_network_instance(instance_ids: Vec) -> Result<(), String> { let instance_ids = instance_ids .into_iter() .filter_map(|id| uuid::Uuid::parse_str(&id).ok()) .collect(); let retained = INSTANCE_MANAGER .retain_network_instance(instance_ids) .map_err(|e| e.to_string())?; println!("instance {:?} retained", retained); Ok(()) } #[tauri::command] fn collect_network_infos() -> Result, String> { let infos = INSTANCE_MANAGER .collect_network_infos() .map_err(|e| e.to_string())?; let mut ret = BTreeMap::new(); for (uuid, info) in infos { ret.insert(uuid.to_string(), info); } Ok(ret) } #[tauri::command] fn get_os_hostname() -> Result { Ok(gethostname::gethostname().to_string_lossy().to_string()) } #[tauri::command] fn set_logging_level(level: String) -> Result<(), String> { #[allow(static_mut_refs)] let sender = unsafe { LOGGER_LEVEL_SENDER.as_ref().unwrap() }; sender.send(level).map_err(|e| e.to_string())?; Ok(()) } #[tauri::command] fn set_tun_fd(instance_id: String, fd: i32) -> Result<(), String> { let uuid = uuid::Uuid::parse_str(&instance_id).map_err(|e| e.to_string())?; INSTANCE_MANAGER .set_tun_fd(&uuid, fd) .map_err(|e| e.to_string())?; Ok(()) } #[cfg(not(target_os = "android"))] fn toggle_window_visibility(app: &tauri::AppHandle) { if let Some(window) = app.get_webview_window("main") { if window.is_visible().unwrap_or_default() { if window.is_minimized().unwrap_or_default() { let _ = window.unminimize(); let _ = window.set_focus(); } else { let _ = window.hide(); } } else { let _ = window.show(); let _ = window.set_focus(); } } } #[cfg(not(target_os = "android"))] fn check_sudo() -> bool { let is_elevated = elevate::Command::is_elevated(); if !is_elevated { let exe_path = std::env::var("APPIMAGE") .ok() .or_else(|| std::env::args().next()) .unwrap_or_default(); let args: Vec = std::env::args().collect(); let mut stdcmd = std::process::Command::new(&exe_path); if args.contains(&AUTOSTART_ARG.to_owned()) { stdcmd.arg(AUTOSTART_ARG); } elevate::Command::new(stdcmd) .output() .expect("Failed to run elevated command"); } is_elevated } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { #[cfg(not(target_os = "android"))] if !check_sudo() { use std::process; process::exit(0); } utils::setup_panic_handler(); let mut builder = tauri::Builder::default(); #[cfg(not(target_os = "android"))] { use tauri_plugin_autostart::MacosLauncher; builder = builder.plugin(tauri_plugin_autostart::init( MacosLauncher::LaunchAgent, Some(vec![AUTOSTART_ARG]), )); } #[cfg(not(any(target_os = "android", target_os = "ios")))] { builder = builder.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| { app.webview_windows() .values() .next() .expect("Sorry, no window found") .set_focus() .expect("Can't Bring Window to Focus"); })); } builder = builder .plugin(tauri_plugin_os::init()) .plugin(tauri_plugin_clipboard_manager::init()) .plugin(tauri_plugin_process::init()) .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_vpnservice::init()); let app = builder .setup(|app| { // for logging config let Ok(log_dir) = app.path().app_log_dir() else { return Ok(()); }; let config = LoggingConfigBuilder::default() .file_logger(FileLoggerConfig { dir: Some(log_dir.to_string_lossy().to_string()), level: None, file: None, }) .build() .map_err(|e| e.to_string())?; let Ok(Some(logger_reinit)) = utils::init_logger(&config, true) else { return Ok(()); }; #[allow(static_mut_refs)] unsafe { LOGGER_LEVEL_SENDER.replace(logger_reinit) }; // for tray icon, menu need to be built in js #[cfg(not(target_os = "android"))] let _tray_menu = TrayIconBuilder::with_id("main") .show_menu_on_left_click(false) .on_tray_icon_event(|tray, event| { if let TrayIconEvent::Click { button: MouseButton::Left, button_state: MouseButtonState::Up, .. } = event { let app = tray.app_handle(); toggle_window_visibility(app); } }) .icon(tauri::image::Image::from_bytes(include_bytes!( "../icons/icon.png" ))?) .icon_as_template(false) .build(app)?; Ok(()) }) .invoke_handler(tauri::generate_handler![ parse_network_config, generate_network_config, run_network_instance, retain_network_instance, collect_network_infos, get_os_hostname, set_logging_level, set_tun_fd, is_autostart, easytier_version, set_dock_visibility ]) .on_window_event(|_win, event| match event { #[cfg(not(target_os = "android"))] tauri::WindowEvent::CloseRequested { api, .. } => { let _ = _win.hide(); api.prevent_close(); } _ => {} }) .build(tauri::generate_context!()) .unwrap(); #[cfg(not(target_os = "macos"))] app.run(|_app, _event| {}); #[cfg(target_os = "macos")] { use tauri::RunEvent; app.run(|app, event| match event { RunEvent::Reopen { .. } => { toggle_window_visibility(app); } _ => {} }); } }