diff --git a/src-tauri/locales/en.json b/src-tauri/locales/en.json index 1913fc2..2424a99 100644 --- a/src-tauri/locales/en.json +++ b/src-tauri/locales/en.json @@ -227,6 +227,11 @@ "Umask": "File Permission Mask", "about": "About", "autostart": "Autostart on Boot", + "autostart_disabled": "Disabled", + "autostart_standard": "Standard (Registry)", + "autostart_high_priority": "High Priority (Task Scheduler)", + "service_mode": "Service Mode", + "service_mode_hint": "Start without UI using --service flag, lower resource usage", "install": "Install", "winfsp_not_installed": "Dependency (WinFsp) needs to be installed to mount storage.", "install_failed": "Installation Failed", diff --git a/src-tauri/locales/zh-cn.json b/src-tauri/locales/zh-cn.json index 7e84b31..d74e67a 100644 --- a/src-tauri/locales/zh-cn.json +++ b/src-tauri/locales/zh-cn.json @@ -230,6 +230,11 @@ "Umask": "文件权限掩码", "about": "关于", "autostart": "开机自启", + "autostart_disabled": "禁用", + "autostart_standard": "标准(注册表)", + "autostart_high_priority": "高优先级(任务计划程序)", + "service_mode": "服务模式", + "service_mode_hint": "使用 --service 参数启动时,不显示界面,资源占用更少", "install": "安装", "winfsp_not_installed": "需要安装依赖(WinFsp),才能挂载存储。", "install_failed": "安装失败", diff --git a/src-tauri/locales/zh-hant.json b/src-tauri/locales/zh-hant.json index 591d565..55915e6 100644 --- a/src-tauri/locales/zh-hant.json +++ b/src-tauri/locales/zh-hant.json @@ -197,6 +197,11 @@ "Umask": "文件許可權遮罩", "about": "關於", "autostart": "開機自啟", + "autostart_disabled": "停用", + "autostart_standard": "標準(登錄檔)", + "autostart_high_priority": "高優先順序(工作排程器)", + "service_mode": "服務模式", + "service_mode_hint": "使用 --service 參數啟動時,不顯示介面,資源佔用更少", "install": "安裝", "winfsp_not_installed": "需要安裝依賴(WinFsp),才能掛載存儲。", "network_share_tip": "提示:如需網路共用掛載的磁碟機,請取消勾選"類比本地硬碟"選項(即使用網路磁碟機模式),並確保WinFsp已正確安裝。部分儲存類型可能不支援網路共用。", diff --git a/src-tauri/src/autostart.rs b/src-tauri/src/autostart.rs new file mode 100644 index 0000000..7ea2725 --- /dev/null +++ b/src-tauri/src/autostart.rs @@ -0,0 +1,116 @@ +//! Windows Task Scheduler based autostart for higher startup priority. +//! +//! On Windows, the standard `tauri-plugin-autostart` uses the registry key +//! `HKCU\Software\Microsoft\Windows\CurrentVersion\Run` which is processed +//! late in the login sequence. Task Scheduler tasks with "At log on" trigger +//! run earlier, providing higher startup priority. +//! +//! This module provides functions to manage a scheduled task as an alternative +//! autostart mechanism with higher priority. + +#[cfg(target_os = "windows")] +use std::process::Command; + +/// Task name used in Windows Task Scheduler +#[cfg(target_os = "windows")] +const TASK_NAME: &str = "NetMount_Autostart"; + +/// Check if the scheduled task exists and is enabled. +/// Returns true if the task is registered and ready to run. +#[cfg(target_os = "windows")] +pub fn is_task_enabled() -> bool { + let output = match Command::new("schtasks.exe") + .args(["/query", "/tn", TASK_NAME, "/fo", "csv", "/nh"]) + .output() + { + Ok(out) => out, + Err(_) => return false, + }; + + if !output.status.success() { + return false; + } + + let stdout = String::from_utf8_lossy(&output.stdout); + // Task exists if output contains status "Ready" or "Running" + // schtasks CSV output format: "TaskName","Next Run Time","Status" + stdout.contains("Ready") || stdout.contains("Running") +} + +/// Create a scheduled task for autostart at logon. +/// Uses "At log on" trigger with limited (user) privileges. +/// +/// # Arguments +/// * `exe_path` - Full path to the NetMount executable +/// * `service_mode` - If true, adds `--service` flag to run headless +#[cfg(target_os = "windows")] +pub fn create_task(exe_path: &str, service_mode: bool) -> Result<(), String> { + // Delete any existing task first + let _ = delete_task(); + + let task_command = if service_mode { + format!("\"{}\" --service", exe_path) + } else { + format!("\"{}\"", exe_path) + }; + + let output = Command::new("schtasks.exe") + .args([ + "/create", + "/tn", + TASK_NAME, + "/tr", + &task_command, + "/sc", + "onlogon", + "/rl", + "limited", + "/f", + ]) + .output() + .map_err(|e| format!("Failed to create scheduled task: {}", e))?; + + if output.status.success() { + Ok(()) + } else { + let stderr = String::from_utf8_lossy(&output.stderr); + Err(format!("Failed to create scheduled task: {}", stderr)) + } +} + +/// Delete the scheduled task. +#[cfg(target_os = "windows")] +pub fn delete_task() -> Result<(), String> { + let output = Command::new("schtasks.exe") + .args(["/delete", "/tn", TASK_NAME, "/f"]) + .output() + .map_err(|e| format!("Failed to delete scheduled task: {}", e))?; + + if output.status.success() { + Ok(()) + } else { + // Task might not exist, which is fine + let stderr = String::from_utf8_lossy(&output.stderr); + if stderr.contains("not found") || stderr.contains("找不到") || stderr.contains("0x1") { + Ok(()) + } else { + Err(format!("Failed to delete scheduled task: {}", stderr)) + } + } +} + +// Non-Windows stubs +#[cfg(not(target_os = "windows"))] +pub fn is_task_enabled() -> bool { + false +} + +#[cfg(not(target_os = "windows"))] +pub fn create_task(_exe_path: &str, _service_mode: bool) -> Result<(), String> { + Err("Task Scheduler is only available on Windows".to_string()) +} + +#[cfg(not(target_os = "windows"))] +pub fn delete_task() -> Result<(), String> { + Err("Task Scheduler is only available on Windows".to_string()) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 390b651..82b6d70 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -255,6 +255,10 @@ pub fn init() -> anyhow::Result<()> { diagnostics::export_diagnostics, get_autostart_state, set_autostart_state, + get_autostart_mode, + set_autostart_mode, + is_service_mode, + is_task_scheduler_available, get_winfsp_install_state, get_available_drive_letter, get_available_ports, diff --git a/src/controller/mainInit.ts b/src/controller/mainInit.ts index fa96da8..593c21a 100644 --- a/src/controller/mainInit.ts +++ b/src/controller/mainInit.ts @@ -1,6 +1,7 @@ import { listenWindow, window as appWindow } from './window' import { nmConfig, readNmConfig, roConfig, runtimeEnv } from '../services/ConfigService' import { setThemeMode } from './setting/setting' +import { isServiceMode } from './setting/setting' import { setLocalized } from './language/localized' import { startRclone } from '../utils/rclone/process' import { startOpenlist } from '../utils/openlist/process' @@ -126,7 +127,10 @@ export async function init(setStartStr: SetStartStrFn) { // 启动定期清理任务(每4小时清理过期缓存) startPeriodicCleanup() - if (!nmConfig.settings.startHide) { + // 检查是否在服务模式下运行(--service 标志) + const serviceMode = await isServiceMode().catch(() => false) + + if (!nmConfig.settings.startHide && !serviceMode) { await appWindow.show() await appWindow.setFocus() } diff --git a/src/controller/setting/setting.ts b/src/controller/setting/setting.ts index 073e3d9..a1b82b3 100644 --- a/src/controller/setting/setting.ts +++ b/src/controller/setting/setting.ts @@ -42,4 +42,37 @@ async function setAutostartState(state: boolean): Promise { return (await invoke('set_autostart_state', { enabled: state })) as boolean } -export { setThemeMode, getAutostartState, setAutostartState } +// 自启模式类型 +type AutostartMode = 'none' | 'registry' | 'task_scheduler' + +//获取自启模式 +async function getAutostartMode(): Promise { + return (await invoke('get_autostart_mode')) as AutostartMode +} + +//设置自启模式 +async function setAutostartMode(mode: AutostartMode): Promise { + return (await invoke('set_autostart_mode', { mode })) as boolean +} + +//检查是否支持任务计划程序(仅 Windows) +async function isTaskSchedulerAvailable(): Promise { + return (await invoke('is_task_scheduler_available')) as boolean +} + +//检查是否在服务模式下运行 +async function isServiceMode(): Promise { + return (await invoke('is_service_mode')) as boolean +} + +export { + setThemeMode, + getAutostartState, + setAutostartState, + getAutostartMode, + setAutostartMode, + isTaskSchedulerAvailable, + isServiceMode, +} + +export type { AutostartMode } diff --git a/src/page/setting/components/GeneralSettings.tsx b/src/page/setting/components/GeneralSettings.tsx index 8e351a5..dcc6c88 100644 --- a/src/page/setting/components/GeneralSettings.tsx +++ b/src/page/setting/components/GeneralSettings.tsx @@ -18,7 +18,9 @@ import { useTranslation } from 'react-i18next' import * as dialog from '@tauri-apps/plugin-dialog' import { getAutostartState, - setAutostartState, + getAutostartMode, + setAutostartMode, + isTaskSchedulerAvailable, setThemeMode, } from '../../../controller/setting/setting' import { setLocalized } from '../../../controller/language/localized' @@ -44,14 +46,25 @@ function hashPassword(password: string): string { export function GeneralSettings(): JSX.Element { const { t } = useTranslation() - const [autostart, setAutostart] = useState() + const [autostartMode, setAutostartModeState] = useState('none') + const [taskSchedulerAvailable, setTaskSchedulerAvailable] = useState(false) const { increment: incrementSettings } = useSettingsStore() const [showPasswordModal, setShowPasswordModal] = useState(false) const [newPassword, setNewPassword] = useState('') const [confirmPassword, setConfirmPassword] = useState('') useEffect(() => { - getAutostartState().then(setAutostart) + // Load autostart mode and task scheduler availability + Promise.all([ + getAutostartMode(), + isTaskSchedulerAvailable(), + ]).then(([mode, available]) => { + setAutostartModeState(mode) + setTaskSchedulerAvailable(available) + }).catch(() => { + // Fallback: check basic autostart state + getAutostartState().then(() => {}) + }) }, []) const handleSetPassword = async () => { @@ -128,13 +141,22 @@ export function GeneralSettings(): JSX.Element { - { - await setAutostartState(value) - setAutostart(value) + const success = await setAutostartMode(value as 'none' | 'registry' | 'task_scheduler') + if (success) { + setAutostartModeState(value) + } }} - /> + style={{ width: '14rem' }} + > + {t('autostart_disabled')} + {t('autostart_standard')} + {taskSchedulerAvailable && ( + {t('autostart_high_priority')} + )} +