mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-04-18 08:21:34 +08:00
refactor: windows autostart (#5941)
* refactor(windows-autostart): switch to scheduled tasks with user/admin split * fix(schtasks): decode stdout/stderr using Windows OEM/ANSI code pages * refactor(ui): remove admin auto-launch warning and clean i18n * feat(windows): user-level auto-launch via task XML * docs: Changelog.md
This commit is contained in:
parent
a67abda72d
commit
65b4d8713d
@ -26,5 +26,6 @@
|
|||||||
- 应用内更新日志支持解析并渲染 HTML 标签
|
- 应用内更新日志支持解析并渲染 HTML 标签
|
||||||
- 性能优化前后端在渲染流量图时的资源
|
- 性能优化前后端在渲染流量图时的资源
|
||||||
- 在 Linux NVIDIA 显卡环境下尝试禁用 WebKit DMABUF 渲染以规避潜在问题
|
- 在 Linux NVIDIA 显卡环境下尝试禁用 WebKit DMABUF 渲染以规避潜在问题
|
||||||
|
- Windows 下自启动改为计划任务实现
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|||||||
@ -113,10 +113,12 @@ winapi = { version = "0.3.9", features = [
|
|||||||
"errhandlingapi",
|
"errhandlingapi",
|
||||||
"minwindef",
|
"minwindef",
|
||||||
"winerror",
|
"winerror",
|
||||||
|
"stringapiset",
|
||||||
"tlhelp32",
|
"tlhelp32",
|
||||||
"processthreadsapi",
|
"processthreadsapi",
|
||||||
"winhttp",
|
"winhttp",
|
||||||
"winreg",
|
"winreg",
|
||||||
|
"winnls",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
use crate::utils::autostart as startup_shortcut;
|
use crate::utils::schtasks as startup_task;
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{Config, IVerge},
|
config::{Config, IVerge},
|
||||||
core::handle::Handle,
|
core::handle::Handle,
|
||||||
singleton,
|
singleton,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clash_verge_logging::{Type, logging, logging_error};
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
use clash_verge_logging::logging_error;
|
||||||
|
use clash_verge_logging::{Type, logging};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use scopeguard::defer;
|
use scopeguard::defer;
|
||||||
use smartstring::alias::String;
|
use smartstring::alias::String;
|
||||||
@ -18,7 +20,10 @@ use std::{
|
|||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use sysproxy::{Autoproxy, GuardMonitor, GuardType, Sysproxy};
|
use sysproxy::{Autoproxy, GuardMonitor, GuardType, Sysproxy};
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
use tauri_plugin_autostart::ManagerExt as _;
|
use tauri_plugin_autostart::ManagerExt as _;
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
use tauri_plugin_clash_verge_sysinfo::is_current_app_handle_admin;
|
||||||
|
|
||||||
pub struct Sysopt {
|
pub struct Sysopt {
|
||||||
update_sysproxy: AtomicBool,
|
update_sysproxy: AtomicBool,
|
||||||
@ -230,35 +235,21 @@ impl Sysopt {
|
|||||||
let is_enable = enable_auto_launch.unwrap_or(false);
|
let is_enable = enable_auto_launch.unwrap_or(false);
|
||||||
logging!(info, Type::System, "Setting auto-launch state to: {:?}", is_enable);
|
logging!(info, Type::System, "Setting auto-launch state to: {:?}", is_enable);
|
||||||
|
|
||||||
// 首先尝试使用快捷方式方法
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
if is_enable {
|
let is_admin = is_current_app_handle_admin(Handle::app_handle());
|
||||||
if let Err(e) = startup_shortcut::create_shortcut().await {
|
startup_task::set_auto_launch(is_enable, is_admin).await
|
||||||
logging!(error, Type::Setup, "创建启动快捷方式失败: {e}");
|
|
||||||
// 如果快捷方式创建失败,回退到原来的方法
|
|
||||||
self.try_original_autostart_method(is_enable);
|
|
||||||
} else {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
} else if let Err(e) = startup_shortcut::remove_shortcut().await {
|
|
||||||
logging!(error, Type::Setup, "删除启动快捷方式失败: {e}");
|
|
||||||
self.try_original_autostart_method(is_enable);
|
|
||||||
} else {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
{
|
{
|
||||||
// 非Windows平台使用原来的方法
|
|
||||||
self.try_original_autostart_method(is_enable);
|
self.try_original_autostart_method(is_enable);
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 尝试使用原来的自启动方法
|
/// 尝试使用原来的自启动方法
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
fn try_original_autostart_method(&self, is_enable: bool) {
|
fn try_original_autostart_method(&self, is_enable: bool) {
|
||||||
let app_handle = Handle::app_handle();
|
let app_handle = Handle::app_handle();
|
||||||
let autostart_manager = app_handle.autolaunch();
|
let autostart_manager = app_handle.autolaunch();
|
||||||
@ -272,24 +263,19 @@ impl Sysopt {
|
|||||||
|
|
||||||
/// 获取当前自启动的实际状态
|
/// 获取当前自启动的实际状态
|
||||||
pub fn get_launch_status(&self) -> Result<bool> {
|
pub fn get_launch_status(&self) -> Result<bool> {
|
||||||
// 首先尝试检查快捷方式是否存在
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
match startup_shortcut::is_shortcut_enabled() {
|
let enabled = startup_task::is_auto_launch_enabled();
|
||||||
Ok(enabled) => {
|
if let Ok(status) = enabled {
|
||||||
logging!(info, Type::System, "快捷方式自启动状态: {enabled}");
|
logging!(info, Type::System, "Auto launch status (scheduled task): {status}");
|
||||||
return Ok(enabled);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
logging!(error, Type::System, "检查快捷方式失败,尝试原来的方法: {e}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
// 回退到原来的方法
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
{
|
||||||
let app_handle = Handle::app_handle();
|
let app_handle = Handle::app_handle();
|
||||||
let autostart_manager = app_handle.autolaunch();
|
let autostart_manager = app_handle.autolaunch();
|
||||||
|
|
||||||
match autostart_manager.is_enabled() {
|
match autostart_manager.is_enabled() {
|
||||||
Ok(status) => {
|
Ok(status) => {
|
||||||
logging!(info, Type::System, "Auto launch status: {status}");
|
logging!(info, Type::System, "Auto launch status: {status}");
|
||||||
@ -301,4 +287,5 @@ impl Sysopt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,132 +0,0 @@
|
|||||||
#[cfg(target_os = "windows")]
|
|
||||||
use anyhow::{Result, anyhow};
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
use clash_verge_logging::{Type, logging};
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
use std::{os::windows::process::CommandExt as _, path::Path, path::PathBuf};
|
|
||||||
|
|
||||||
/// Windows 下的开机启动文件夹路径
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
pub fn get_startup_dir() -> Result<PathBuf> {
|
|
||||||
let appdata = std::env::var("APPDATA").map_err(|_| anyhow!("无法获取 APPDATA 环境变量"))?;
|
|
||||||
|
|
||||||
let startup_dir = Path::new(&appdata)
|
|
||||||
.join("Microsoft")
|
|
||||||
.join("Windows")
|
|
||||||
.join("Start Menu")
|
|
||||||
.join("Programs")
|
|
||||||
.join("Startup");
|
|
||||||
|
|
||||||
if !startup_dir.exists() {
|
|
||||||
return Err(anyhow!("Startup 目录不存在: {:?}", startup_dir));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(startup_dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取当前可执行文件路径
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
pub fn get_exe_path() -> Result<PathBuf> {
|
|
||||||
let exe_path = std::env::current_exe().map_err(|e| anyhow!("无法获取当前可执行文件路径: {}", e))?;
|
|
||||||
|
|
||||||
Ok(exe_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 创建快捷方式
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
pub async fn create_shortcut() -> Result<()> {
|
|
||||||
use crate::utils::dirs::PathBufExec as _;
|
|
||||||
|
|
||||||
let exe_path = get_exe_path()?;
|
|
||||||
let startup_dir = get_startup_dir()?;
|
|
||||||
let old_shortcut_path = startup_dir.join("Clash-Verge.lnk");
|
|
||||||
let new_shortcut_path = startup_dir.join("Clash Verge.lnk");
|
|
||||||
|
|
||||||
// 移除旧的快捷方式
|
|
||||||
let _ = old_shortcut_path
|
|
||||||
.remove_if_exists()
|
|
||||||
.await
|
|
||||||
.inspect(|_| {
|
|
||||||
logging!(info, Type::Setup, "成功移除旧启动快捷方式");
|
|
||||||
})
|
|
||||||
.inspect_err(|err| {
|
|
||||||
logging!(error, Type::Setup, "移除旧启动快捷方式失败: {err}");
|
|
||||||
});
|
|
||||||
|
|
||||||
// 如果新快捷方式已存在,直接返回成功
|
|
||||||
if new_shortcut_path.exists() {
|
|
||||||
logging!(info, Type::Setup, "启动快捷方式已存在");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用 PowerShell 创建快捷方式
|
|
||||||
let powershell_command = format!(
|
|
||||||
"$WshShell = New-Object -ComObject WScript.Shell; \
|
|
||||||
$Shortcut = $WshShell.CreateShortcut('{}'); \
|
|
||||||
$Shortcut.TargetPath = '{}'; \
|
|
||||||
$Shortcut.Save()",
|
|
||||||
new_shortcut_path.to_string_lossy().replace("\\", "\\\\"),
|
|
||||||
exe_path.to_string_lossy().replace("\\", "\\\\")
|
|
||||||
);
|
|
||||||
|
|
||||||
let output = std::process::Command::new("powershell")
|
|
||||||
.args(["-Command", &powershell_command])
|
|
||||||
// 隐藏 PowerShell 窗口
|
|
||||||
.creation_flags(0x08000000) // CREATE_NO_WINDOW
|
|
||||||
.output()
|
|
||||||
.map_err(|e| anyhow!("执行 PowerShell 命令失败: {}", e))?;
|
|
||||||
|
|
||||||
if !output.status.success() {
|
|
||||||
let error_msg = String::from_utf8_lossy(&output.stderr);
|
|
||||||
return Err(anyhow!("创建快捷方式失败: {}", error_msg));
|
|
||||||
}
|
|
||||||
|
|
||||||
logging!(info, Type::Setup, "成功创建启动快捷方式");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 删除快捷方式
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
pub async fn remove_shortcut() -> Result<()> {
|
|
||||||
use crate::utils::dirs::PathBufExec as _;
|
|
||||||
|
|
||||||
let startup_dir = get_startup_dir()?;
|
|
||||||
let old_shortcut_path = startup_dir.join("Clash-Verge.lnk");
|
|
||||||
let new_shortcut_path = startup_dir.join("Clash Verge.lnk");
|
|
||||||
|
|
||||||
let mut removed_any = false;
|
|
||||||
|
|
||||||
let _ = old_shortcut_path
|
|
||||||
.remove_if_exists()
|
|
||||||
.await
|
|
||||||
.inspect(|_| {
|
|
||||||
logging!(info, Type::Setup, "成功删除旧启动快捷方式");
|
|
||||||
removed_any = true;
|
|
||||||
})
|
|
||||||
.inspect_err(|err| {
|
|
||||||
logging!(error, Type::Setup, "删除旧启动快捷方式失败: {err}");
|
|
||||||
});
|
|
||||||
|
|
||||||
let _ = new_shortcut_path
|
|
||||||
.remove_if_exists()
|
|
||||||
.await
|
|
||||||
.inspect(|_| {
|
|
||||||
logging!(info, Type::Setup, "成功删除启动快捷方式");
|
|
||||||
removed_any = true;
|
|
||||||
})
|
|
||||||
.inspect_err(|err| {
|
|
||||||
logging!(error, Type::Setup, "删除启动快捷方式失败: {err}");
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 检查快捷方式是否存在
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
pub fn is_shortcut_enabled() -> Result<bool> {
|
|
||||||
let startup_dir = get_startup_dir()?;
|
|
||||||
let new_shortcut_path = startup_dir.join("Clash Verge.lnk");
|
|
||||||
|
|
||||||
Ok(new_shortcut_path.exists())
|
|
||||||
}
|
|
||||||
@ -1,4 +1,3 @@
|
|||||||
pub mod autostart;
|
|
||||||
pub mod dirs;
|
pub mod dirs;
|
||||||
pub mod format;
|
pub mod format;
|
||||||
pub mod help;
|
pub mod help;
|
||||||
@ -9,6 +8,8 @@ pub mod linux;
|
|||||||
pub mod network;
|
pub mod network;
|
||||||
pub mod notification;
|
pub mod notification;
|
||||||
pub mod resolve;
|
pub mod resolve;
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
pub mod schtasks;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
pub mod singleton;
|
pub mod singleton;
|
||||||
pub mod tmpl;
|
pub mod tmpl;
|
||||||
|
|||||||
403
src-tauri/src/utils/schtasks.rs
Normal file
403
src-tauri/src/utils/schtasks.rs
Normal file
@ -0,0 +1,403 @@
|
|||||||
|
use crate::utils::dirs::{self, PathBufExec as _};
|
||||||
|
use anyhow::{Result, anyhow};
|
||||||
|
use clash_verge_logging::{Type, logging};
|
||||||
|
use std::fs;
|
||||||
|
use std::os::windows::process::CommandExt as _;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process::{Command, Output};
|
||||||
|
use winapi::um::stringapiset::MultiByteToWideChar;
|
||||||
|
use winapi::um::winnls::{GetACP, GetOEMCP};
|
||||||
|
|
||||||
|
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||||
|
const TASK_NAME_USER: &str = "Clash Verge";
|
||||||
|
const TASK_NAME_ADMIN: &str = "Clash Verge (Admin)";
|
||||||
|
const TASK_XML_DIR: &str = "tasks";
|
||||||
|
const TASK_XML_USER: &str = "clash-verge-task-user.xml";
|
||||||
|
const TASK_XML_ADMIN: &str = "clash-verge-task-admin.xml";
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum TaskMode {
|
||||||
|
User,
|
||||||
|
Admin,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TaskMode {
|
||||||
|
const fn name(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::User => TASK_NAME_USER,
|
||||||
|
Self::Admin => TASK_NAME_ADMIN,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn label(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::User => "user",
|
||||||
|
Self::Admin => "admin",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn xml_run_level(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::User => "LeastPrivilege",
|
||||||
|
Self::Admin => "HighestAvailable",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn xml_file_name(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::User => TASK_XML_USER,
|
||||||
|
Self::Admin => TASK_XML_ADMIN,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_exe_path() -> Result<PathBuf> {
|
||||||
|
let exe_path = std::env::current_exe().map_err(|e| anyhow!("failed to get exe path: {}", e))?;
|
||||||
|
Ok(exe_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_task_user_id() -> Result<String> {
|
||||||
|
let username = std::env::var_os("USERNAME")
|
||||||
|
.or_else(|| std::env::var_os("USER"))
|
||||||
|
.ok_or_else(|| anyhow!("failed to get current user name"))?;
|
||||||
|
let username = username.to_string_lossy();
|
||||||
|
let username = username.trim();
|
||||||
|
if username.is_empty() {
|
||||||
|
return Err(anyhow!("current user name is empty"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let domain = std::env::var_os("USERDOMAIN")
|
||||||
|
.or_else(|| std::env::var_os("COMPUTERNAME"))
|
||||||
|
.map(|value| value.to_string_lossy().to_string());
|
||||||
|
|
||||||
|
if let Some(domain) = domain {
|
||||||
|
let domain = domain.trim();
|
||||||
|
if !domain.is_empty() {
|
||||||
|
return Ok(format!("{domain}\\{username}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(username.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_startup_dir() -> Result<PathBuf> {
|
||||||
|
let appdata = std::env::var("APPDATA").map_err(|_| anyhow!("failed to read APPDATA env var"))?;
|
||||||
|
let startup_dir = Path::new(&appdata)
|
||||||
|
.join("Microsoft")
|
||||||
|
.join("Windows")
|
||||||
|
.join("Start Menu")
|
||||||
|
.join("Programs")
|
||||||
|
.join("Startup");
|
||||||
|
|
||||||
|
if !startup_dir.exists() {
|
||||||
|
return Err(anyhow!("startup folder does not exist: {:?}", startup_dir));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(startup_dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cleanup_legacy_shortcuts() -> Result<()> {
|
||||||
|
let startup_dir = get_startup_dir()?;
|
||||||
|
let old_shortcut = startup_dir.join("Clash-Verge.lnk");
|
||||||
|
let new_shortcut = startup_dir.join("Clash Verge.lnk");
|
||||||
|
|
||||||
|
old_shortcut.remove_if_exists().await?;
|
||||||
|
new_shortcut.remove_if_exists().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn task_xml_path(mode: TaskMode) -> Result<PathBuf> {
|
||||||
|
let dir = dirs::app_home_dir()?.join(TASK_XML_DIR);
|
||||||
|
fs::create_dir_all(&dir).map_err(|e| anyhow!("failed to create task xml dir: {}", e))?;
|
||||||
|
Ok(dir.join(mode.xml_file_name()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn xml_escape(value: &str) -> String {
|
||||||
|
let mut escaped = String::with_capacity(value.len());
|
||||||
|
for ch in value.chars() {
|
||||||
|
match ch {
|
||||||
|
'&' => escaped.push_str("&"),
|
||||||
|
'<' => escaped.push_str("<"),
|
||||||
|
'>' => escaped.push_str(">"),
|
||||||
|
'"' => escaped.push_str("""),
|
||||||
|
'\'' => escaped.push_str("'"),
|
||||||
|
_ => escaped.push(ch),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
escaped
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_task_xml(mode: TaskMode) -> Result<String> {
|
||||||
|
let exe_path = get_exe_path()?.to_string_lossy().to_string();
|
||||||
|
let exe_path = xml_escape(&exe_path);
|
||||||
|
let user_id = xml_escape(&get_task_user_id()?);
|
||||||
|
Ok(format!(
|
||||||
|
r#"<?xml version="1.0" encoding="UTF-16"?>
|
||||||
|
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
||||||
|
<Triggers>
|
||||||
|
<LogonTrigger>
|
||||||
|
<Enabled>true</Enabled>
|
||||||
|
<Delay>PT3S</Delay>
|
||||||
|
<UserId>{}</UserId>
|
||||||
|
</LogonTrigger>
|
||||||
|
</Triggers>
|
||||||
|
<Principals>
|
||||||
|
<Principal id="Author">
|
||||||
|
<UserId>{}</UserId>
|
||||||
|
<LogonType>InteractiveToken</LogonType>
|
||||||
|
<RunLevel>{}</RunLevel>
|
||||||
|
</Principal>
|
||||||
|
</Principals>
|
||||||
|
<Settings>
|
||||||
|
<MultipleInstancesPolicy>Parallel</MultipleInstancesPolicy>
|
||||||
|
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
|
||||||
|
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
|
||||||
|
<AllowHardTerminate>false</AllowHardTerminate>
|
||||||
|
<StartWhenAvailable>false</StartWhenAvailable>
|
||||||
|
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
|
||||||
|
<IdleSettings>
|
||||||
|
<StopOnIdleEnd>false</StopOnIdleEnd>
|
||||||
|
<RestartOnIdle>false</RestartOnIdle>
|
||||||
|
</IdleSettings>
|
||||||
|
<AllowStartOnDemand>true</AllowStartOnDemand>
|
||||||
|
<Enabled>true</Enabled>
|
||||||
|
<Hidden>false</Hidden>
|
||||||
|
<RunOnlyIfIdle>false</RunOnlyIfIdle>
|
||||||
|
<WakeToRun>false</WakeToRun>
|
||||||
|
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
|
||||||
|
<Priority>3</Priority>
|
||||||
|
</Settings>
|
||||||
|
<Actions Context="Author">
|
||||||
|
<Exec>
|
||||||
|
<Command>{}</Command>
|
||||||
|
</Exec>
|
||||||
|
</Actions>
|
||||||
|
</Task>
|
||||||
|
"#,
|
||||||
|
user_id,
|
||||||
|
user_id,
|
||||||
|
mode.xml_run_level(),
|
||||||
|
exe_path
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_utf16le_with_bom(content: &str) -> Vec<u8> {
|
||||||
|
let mut bytes = Vec::with_capacity(2 + content.len() * 2);
|
||||||
|
bytes.extend_from_slice(&[0xFF, 0xFE]);
|
||||||
|
for unit in content.encode_utf16() {
|
||||||
|
bytes.extend_from_slice(&unit.to_le_bytes());
|
||||||
|
}
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_task_xml(mode: TaskMode) -> Result<PathBuf> {
|
||||||
|
let task_xml = build_task_xml(mode)?;
|
||||||
|
let task_xml_path = task_xml_path(mode)?;
|
||||||
|
let encoded = encode_utf16le_with_bom(&task_xml);
|
||||||
|
fs::write(&task_xml_path, encoded).map_err(|e| anyhow!("failed to write task xml: {}", e))?;
|
||||||
|
Ok(task_xml_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_with_code_page(bytes: &[u8], code_page: u32) -> Option<String> {
|
||||||
|
if bytes.is_empty() {
|
||||||
|
return Some(String::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let len = bytes.len();
|
||||||
|
if len > i32::MAX as usize {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let required = unsafe {
|
||||||
|
MultiByteToWideChar(
|
||||||
|
code_page,
|
||||||
|
0,
|
||||||
|
bytes.as_ptr() as *const i8,
|
||||||
|
len as i32,
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if required == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut wide = vec![0u16; required as usize];
|
||||||
|
let written = unsafe {
|
||||||
|
MultiByteToWideChar(
|
||||||
|
code_page,
|
||||||
|
0,
|
||||||
|
bytes.as_ptr() as *const i8,
|
||||||
|
len as i32,
|
||||||
|
wide.as_mut_ptr(),
|
||||||
|
required,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if written == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
wide.truncate(written as usize);
|
||||||
|
Some(String::from_utf16_lossy(&wide))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_console_output(bytes: &[u8]) -> String {
|
||||||
|
if let Ok(text) = std::str::from_utf8(bytes) {
|
||||||
|
return text.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
let oem = unsafe { GetOEMCP() };
|
||||||
|
if let Some(text) = decode_with_code_page(bytes, oem) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
let acp = unsafe { GetACP() };
|
||||||
|
if let Some(text) = decode_with_code_page(bytes, acp) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
String::from_utf8_lossy(bytes).to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_message(output: &Output) -> String {
|
||||||
|
let stdout = decode_console_output(&output.stdout);
|
||||||
|
let stderr = decode_console_output(&output.stderr);
|
||||||
|
let stdout = stdout.trim();
|
||||||
|
let stderr = stderr.trim();
|
||||||
|
|
||||||
|
match (stdout.is_empty(), stderr.is_empty()) {
|
||||||
|
(true, true) => "unknown error".to_string(),
|
||||||
|
(false, true) => stdout.to_string(),
|
||||||
|
(true, false) => stderr.to_string(),
|
||||||
|
(false, false) => format!("{stdout} | {stderr}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn schtasks_output(mut cmd: Command) -> Result<Output> {
|
||||||
|
cmd.creation_flags(CREATE_NO_WINDOW)
|
||||||
|
.output()
|
||||||
|
.map_err(|e| anyhow!("failed to execute schtasks: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_task_enabled(mode: TaskMode) -> Result<bool> {
|
||||||
|
let output = schtasks_output({
|
||||||
|
let mut cmd = Command::new("schtasks");
|
||||||
|
cmd.args(["/Query", "/TN", mode.name()]);
|
||||||
|
cmd
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(output.status.success())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_task(mode: TaskMode) -> Result<()> {
|
||||||
|
let task_xml_path = write_task_xml(mode)?;
|
||||||
|
let output = schtasks_output({
|
||||||
|
let mut cmd = Command::new("schtasks");
|
||||||
|
cmd.args(["/Create", "/TN", mode.name(), "/XML"]);
|
||||||
|
cmd.arg(&task_xml_path);
|
||||||
|
cmd.arg("/F");
|
||||||
|
cmd
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"failed to create {} task: {}",
|
||||||
|
mode.label(),
|
||||||
|
output_message(&output)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
logging!(info, Type::Setup, "Created {} auto-launch task", mode.label());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_task(mode: TaskMode) -> Result<()> {
|
||||||
|
let output = schtasks_output({
|
||||||
|
let mut cmd = Command::new("schtasks");
|
||||||
|
cmd.args(["/Delete", "/TN", mode.name(), "/F"]);
|
||||||
|
cmd
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if output.status.success() {
|
||||||
|
logging!(info, Type::Setup, "Removed {} auto-launch task", mode.label());
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !is_task_enabled(mode)? {
|
||||||
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Setup,
|
||||||
|
"{} auto-launch task not found, skipping removal",
|
||||||
|
mode.label()
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(anyhow!(
|
||||||
|
"failed to remove {} task: {}",
|
||||||
|
mode.label(),
|
||||||
|
output_message(&output)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_auto_launch(is_enable: bool, is_admin: bool) -> Result<()> {
|
||||||
|
let target = if is_admin { TaskMode::Admin } else { TaskMode::User };
|
||||||
|
let other = if is_admin { TaskMode::User } else { TaskMode::Admin };
|
||||||
|
|
||||||
|
if let Err(err) = cleanup_legacy_shortcuts().await {
|
||||||
|
logging!(warn, Type::Setup, "Failed to cleanup legacy startup shortcuts: {}", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_enable {
|
||||||
|
if is_admin {
|
||||||
|
create_task(target)?;
|
||||||
|
if let Err(err) = remove_task(other) {
|
||||||
|
let _ = remove_task(target);
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if is_task_enabled(other)? {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"admin auto-launch task exists; run the app as administrator to remove it before creating a user task"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
create_task(target)?;
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_admin {
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
if let Err(err) = remove_task(TaskMode::User) {
|
||||||
|
errors.push(err);
|
||||||
|
}
|
||||||
|
if let Err(err) = remove_task(TaskMode::Admin) {
|
||||||
|
errors.push(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(err) = errors.into_iter().next() {
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_task(TaskMode::User)?;
|
||||||
|
if is_task_enabled(TaskMode::Admin)? {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"admin auto-launch task exists; run the app as administrator to remove it"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_auto_launch_enabled() -> Result<bool> {
|
||||||
|
if is_task_enabled(TaskMode::Admin)? {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
is_task_enabled(TaskMode::User)
|
||||||
|
}
|
||||||
@ -1,19 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
InfoOutlined,
|
InfoOutlined,
|
||||||
SettingsOutlined,
|
SettingsOutlined,
|
||||||
WarningOutlined,
|
|
||||||
AdminPanelSettingsOutlined,
|
AdminPanelSettingsOutlined,
|
||||||
DnsOutlined,
|
DnsOutlined,
|
||||||
ExtensionOutlined,
|
ExtensionOutlined,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import {
|
import { Typography, Stack, Divider, Chip, IconButton } from "@mui/material";
|
||||||
Typography,
|
|
||||||
Stack,
|
|
||||||
Divider,
|
|
||||||
Chip,
|
|
||||||
IconButton,
|
|
||||||
Tooltip,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
import { useCallback, useEffect, useMemo, useReducer } from "react";
|
import { useCallback, useEffect, useMemo, useReducer } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@ -303,13 +295,6 @@ export const SystemInfoCard = () => {
|
|||||||
{t("home.components.systemInfo.fields.autoLaunch")}
|
{t("home.components.systemInfo.fields.autoLaunch")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Stack direction="row" spacing={1} alignItems="center">
|
<Stack direction="row" spacing={1} alignItems="center">
|
||||||
{isAdminMode && (
|
|
||||||
<Tooltip
|
|
||||||
title={t("home.components.systemInfo.tooltips.autoLaunchAdmin")}
|
|
||||||
>
|
|
||||||
<WarningOutlined sx={{ color: "warning.main", fontSize: 20 }} />
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
<Chip
|
<Chip
|
||||||
size="small"
|
size="small"
|
||||||
label={
|
label={
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import { WarningRounded } from "@mui/icons-material";
|
|
||||||
import { Tooltip } from "@mui/material";
|
|
||||||
import React, { useRef } from "react";
|
import React, { useRef } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
@ -7,9 +5,7 @@ import { mutate } from "swr";
|
|||||||
import { DialogRef, Switch } from "@/components/base";
|
import { DialogRef, Switch } from "@/components/base";
|
||||||
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
||||||
import ProxyControlSwitches from "@/components/shared/proxy-control-switches";
|
import ProxyControlSwitches from "@/components/shared/proxy-control-switches";
|
||||||
import { useSystemState } from "@/hooks/use-system-state";
|
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { showNotice } from "@/services/notice-service";
|
|
||||||
|
|
||||||
import { GuardState } from "./mods/guard-state";
|
import { GuardState } from "./mods/guard-state";
|
||||||
import { SettingList, SettingItem } from "./mods/setting-comp";
|
import { SettingList, SettingItem } from "./mods/setting-comp";
|
||||||
@ -25,8 +21,6 @@ const SettingSystem = ({ onError }: Props) => {
|
|||||||
|
|
||||||
const { verge, mutateVerge, patchVerge } = useVerge();
|
const { verge, mutateVerge, patchVerge } = useVerge();
|
||||||
|
|
||||||
const { isAdminMode } = useSystemState();
|
|
||||||
|
|
||||||
const { enable_auto_launch, enable_silent_start } = verge ?? {};
|
const { enable_auto_launch, enable_silent_start } = verge ?? {};
|
||||||
|
|
||||||
const sysproxyRef = useRef<DialogRef>(null);
|
const sysproxyRef = useRef<DialogRef>(null);
|
||||||
@ -55,34 +49,16 @@ const SettingSystem = ({ onError }: Props) => {
|
|||||||
onError={onError}
|
onError={onError}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem label={t("settings.sections.system.fields.autoLaunch")}>
|
||||||
label={t("settings.sections.system.fields.autoLaunch")}
|
|
||||||
extra={
|
|
||||||
isAdminMode && (
|
|
||||||
<Tooltip
|
|
||||||
title={t("settings.sections.system.tooltips.autoLaunchAdmin")}
|
|
||||||
>
|
|
||||||
<WarningRounded sx={{ color: "warning.main", mr: 1 }} />
|
|
||||||
</Tooltip>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<GuardState
|
<GuardState
|
||||||
value={enable_auto_launch ?? false}
|
value={enable_auto_launch ?? false}
|
||||||
valueProps="checked"
|
valueProps="checked"
|
||||||
onCatch={onError}
|
onCatch={onError}
|
||||||
onFormat={onSwitchFormat}
|
onFormat={onSwitchFormat}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
// 移除管理员模式检查提示
|
|
||||||
onChangeData({ enable_auto_launch: e });
|
onChangeData({ enable_auto_launch: e });
|
||||||
}}
|
}}
|
||||||
onGuard={async (e) => {
|
onGuard={async (e) => {
|
||||||
if (isAdminMode) {
|
|
||||||
showNotice.info(
|
|
||||||
"settings.sections.system.tooltips.autoLaunchAdmin",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 先触发UI更新立即看到反馈
|
// 先触发UI更新立即看到反馈
|
||||||
onChangeData({ enable_auto_launch: e });
|
onChangeData({ enable_auto_launch: e });
|
||||||
|
|||||||
@ -62,9 +62,6 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"settings": "الإعدادات"
|
"settings": "الإعدادات"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
|
||||||
"autoLaunchAdmin": "Administrator mode may not support auto launch"
|
|
||||||
},
|
|
||||||
"badges": {
|
"badges": {
|
||||||
"adminMode": "Administrator Mode",
|
"adminMode": "Administrator Mode",
|
||||||
"serviceMode": "وضع الخدمة",
|
"serviceMode": "وضع الخدمة",
|
||||||
|
|||||||
@ -15,7 +15,6 @@
|
|||||||
"systemProxy": "وكيل النظام"
|
"systemProxy": "وكيل النظام"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"autoLaunchAdmin": "Administrator mode may not support auto launch",
|
|
||||||
"silentStart": "بدء البرنامج في الخلفية دون عرض الواجهة"
|
"silentStart": "بدء البرنامج في الخلفية دون عرض الواجهة"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
|
|||||||
@ -62,9 +62,6 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"settings": "Einstellungen"
|
"settings": "Einstellungen"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
|
||||||
"autoLaunchAdmin": "Der Administrator-Modus unterstützt möglicherweise keine automatische Startfunktion."
|
|
||||||
},
|
|
||||||
"badges": {
|
"badges": {
|
||||||
"adminMode": "Administrator-Modus",
|
"adminMode": "Administrator-Modus",
|
||||||
"serviceMode": "Service-Modus",
|
"serviceMode": "Service-Modus",
|
||||||
|
|||||||
@ -15,7 +15,6 @@
|
|||||||
"systemProxy": "Systemproxy"
|
"systemProxy": "Systemproxy"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"autoLaunchAdmin": "Der Administrator-Modus unterstützt möglicherweise keine automatische Startfunktion.",
|
|
||||||
"silentStart": "Die Anwendung wird im Hintergrund gestartet, ohne dass das Programmfenster angezeigt wird."
|
"silentStart": "Die Anwendung wird im Hintergrund gestartet, ohne dass das Programmfenster angezeigt wird."
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
|
|||||||
@ -62,9 +62,6 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"settings": "Settings"
|
"settings": "Settings"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
|
||||||
"autoLaunchAdmin": "Administrator mode may not support auto launch"
|
|
||||||
},
|
|
||||||
"badges": {
|
"badges": {
|
||||||
"adminMode": "Administrator Mode",
|
"adminMode": "Administrator Mode",
|
||||||
"serviceMode": "Service Mode",
|
"serviceMode": "Service Mode",
|
||||||
|
|||||||
@ -15,7 +15,6 @@
|
|||||||
"systemProxy": "System Proxy"
|
"systemProxy": "System Proxy"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"autoLaunchAdmin": "Administrator mode may not support auto launch",
|
|
||||||
"silentStart": "Start the program in background mode without displaying the panel"
|
"silentStart": "Start the program in background mode without displaying the panel"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
|
|||||||
@ -62,9 +62,6 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"settings": "Ajustes"
|
"settings": "Ajustes"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
|
||||||
"autoLaunchAdmin": "El modo de administrador puede no admitir el inicio automático."
|
|
||||||
},
|
|
||||||
"badges": {
|
"badges": {
|
||||||
"adminMode": "Modo de administrador",
|
"adminMode": "Modo de administrador",
|
||||||
"serviceMode": "Modo de servicio",
|
"serviceMode": "Modo de servicio",
|
||||||
|
|||||||
@ -15,7 +15,6 @@
|
|||||||
"systemProxy": "Proxy del sistema"
|
"systemProxy": "Proxy del sistema"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"autoLaunchAdmin": "El modo de administrador puede no admitir el inicio automático.",
|
|
||||||
"silentStart": "El programa se ejecutará en segundo plano al iniciarse y no mostrará el panel."
|
"silentStart": "El programa se ejecutará en segundo plano al iniciarse y no mostrará el panel."
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
|
|||||||
@ -62,9 +62,6 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"settings": "تنظیمات"
|
"settings": "تنظیمات"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
|
||||||
"autoLaunchAdmin": "Administrator mode may not support auto launch"
|
|
||||||
},
|
|
||||||
"badges": {
|
"badges": {
|
||||||
"adminMode": "Administrator Mode",
|
"adminMode": "Administrator Mode",
|
||||||
"serviceMode": "حالت سرویس",
|
"serviceMode": "حالت سرویس",
|
||||||
|
|||||||
@ -15,7 +15,6 @@
|
|||||||
"systemProxy": "پراکسی سیستم"
|
"systemProxy": "پراکسی سیستم"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"autoLaunchAdmin": "حالت ادمین ممکن است از راهاندازی خودکار پشتیبانی نکند",
|
|
||||||
"silentStart": "برنامه را در حالت پسزمینه بدون نمایش پانل اجرا کنید"
|
"silentStart": "برنامه را در حالت پسزمینه بدون نمایش پانل اجرا کنید"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
|
|||||||
@ -62,9 +62,6 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"settings": "Pengaturan"
|
"settings": "Pengaturan"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
|
||||||
"autoLaunchAdmin": "Administrator mode may not support auto launch"
|
|
||||||
},
|
|
||||||
"badges": {
|
"badges": {
|
||||||
"adminMode": "Administrator Mode",
|
"adminMode": "Administrator Mode",
|
||||||
"serviceMode": "Mode Layanan",
|
"serviceMode": "Mode Layanan",
|
||||||
|
|||||||
@ -15,7 +15,6 @@
|
|||||||
"systemProxy": "Proksi Sistem"
|
"systemProxy": "Proksi Sistem"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"autoLaunchAdmin": "Administrator mode may not support auto launch",
|
|
||||||
"silentStart": "Mulai program dalam mode latar belakang tanpa menampilkan panel"
|
"silentStart": "Mulai program dalam mode latar belakang tanpa menampilkan panel"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
|
|||||||
@ -62,9 +62,6 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"settings": "設定"
|
"settings": "設定"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
|
||||||
"autoLaunchAdmin": "管理者モードでは起動時の自動起動がサポートされない場合があります。"
|
|
||||||
},
|
|
||||||
"badges": {
|
"badges": {
|
||||||
"adminMode": "管理者モード",
|
"adminMode": "管理者モード",
|
||||||
"serviceMode": "サービスモード",
|
"serviceMode": "サービスモード",
|
||||||
|
|||||||
@ -15,7 +15,6 @@
|
|||||||
"systemProxy": "システムプロキシ"
|
"systemProxy": "システムプロキシ"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"autoLaunchAdmin": "管理者モードでは起動時の自動起動がサポートされない場合があります。",
|
|
||||||
"silentStart": "アプリケーションを起動すると、バックグラウンドモードで実行され、アプリケーションパネルは表示されません。"
|
"silentStart": "アプリケーションを起動すると、バックグラウンドモードで実行され、アプリケーションパネルは表示されません。"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
|
|||||||
@ -62,9 +62,6 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"settings": "설정"
|
"settings": "설정"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
|
||||||
"autoLaunchAdmin": "관리자 모드에서는 자동 실행이 지원되지 않을 수 있습니다"
|
|
||||||
},
|
|
||||||
"badges": {
|
"badges": {
|
||||||
"adminMode": "관리자 모드",
|
"adminMode": "관리자 모드",
|
||||||
"serviceMode": "서비스 모드",
|
"serviceMode": "서비스 모드",
|
||||||
|
|||||||
@ -15,7 +15,6 @@
|
|||||||
"systemProxy": "시스템 프록시"
|
"systemProxy": "시스템 프록시"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"autoLaunchAdmin": "관리자 모드에서는 자동 실행이 지원되지 않을 수 있습니다",
|
|
||||||
"silentStart": "패널을 표시하지 않고 백그라운드에서 시작합니다"
|
"silentStart": "패널을 표시하지 않고 백그라운드에서 시작합니다"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
|
|||||||
@ -62,9 +62,6 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"settings": "Настройки"
|
"settings": "Настройки"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
|
||||||
"autoLaunchAdmin": "Administrator mode may not support auto launch"
|
|
||||||
},
|
|
||||||
"badges": {
|
"badges": {
|
||||||
"adminMode": "Administrator Mode",
|
"adminMode": "Administrator Mode",
|
||||||
"serviceMode": "Режим системной службы",
|
"serviceMode": "Режим системной службы",
|
||||||
|
|||||||
@ -15,7 +15,6 @@
|
|||||||
"systemProxy": "Системный прокси"
|
"systemProxy": "Системный прокси"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"autoLaunchAdmin": "Administrator mode may not support auto launch",
|
|
||||||
"silentStart": "Запускать программу в фоновом режиме без отображения панели"
|
"silentStart": "Запускать программу в фоновом режиме без отображения панели"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
|
|||||||
@ -62,9 +62,6 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"settings": "Ayarlar"
|
"settings": "Ayarlar"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
|
||||||
"autoLaunchAdmin": "Yönetici modu otomatik başlatmayı desteklemeyebilir"
|
|
||||||
},
|
|
||||||
"badges": {
|
"badges": {
|
||||||
"adminMode": "Yönetici Modu",
|
"adminMode": "Yönetici Modu",
|
||||||
"serviceMode": "Hizmet Modu",
|
"serviceMode": "Hizmet Modu",
|
||||||
|
|||||||
@ -15,7 +15,6 @@
|
|||||||
"systemProxy": "Sistem Vekil'i"
|
"systemProxy": "Sistem Vekil'i"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"autoLaunchAdmin": "Yönetici modu otomatik başlatmayı desteklemeyebilir",
|
|
||||||
"silentStart": "Programı paneli görüntülemeden arka plan modunda başlatır"
|
"silentStart": "Programı paneli görüntülemeden arka plan modunda başlatır"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
|
|||||||
@ -62,9 +62,6 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"settings": "Көйләүләр"
|
"settings": "Көйләүләр"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
|
||||||
"autoLaunchAdmin": "Administrator mode may not support auto launch"
|
|
||||||
},
|
|
||||||
"badges": {
|
"badges": {
|
||||||
"adminMode": "Administrator Mode",
|
"adminMode": "Administrator Mode",
|
||||||
"serviceMode": "Сервис режимы",
|
"serviceMode": "Сервис режимы",
|
||||||
|
|||||||
@ -15,7 +15,6 @@
|
|||||||
"systemProxy": "Системалы прокси"
|
"systemProxy": "Системалы прокси"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"autoLaunchAdmin": "Administrator mode may not support auto launch",
|
|
||||||
"silentStart": "Программаны фоновый режимда, тәрәзәсез эшләтеп җибәрү"
|
"silentStart": "Программаны фоновый режимда, тәрәзәсез эшләтеп җибәрү"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
|
|||||||
@ -62,9 +62,6 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"settings": "设置"
|
"settings": "设置"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
|
||||||
"autoLaunchAdmin": "管理员模式可能不支持开机自启"
|
|
||||||
},
|
|
||||||
"badges": {
|
"badges": {
|
||||||
"adminMode": "管理员模式",
|
"adminMode": "管理员模式",
|
||||||
"serviceMode": "服务模式",
|
"serviceMode": "服务模式",
|
||||||
|
|||||||
@ -15,7 +15,6 @@
|
|||||||
"systemProxy": "系统代理"
|
"systemProxy": "系统代理"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"autoLaunchAdmin": "管理员模式可能不支持开机自启",
|
|
||||||
"silentStart": "程序启动时以后台模式运行,不显示程序面板"
|
"silentStart": "程序启动时以后台模式运行,不显示程序面板"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
|
|||||||
@ -62,9 +62,6 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"settings": "設定"
|
"settings": "設定"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
|
||||||
"autoLaunchAdmin": "管理員模式可能不支援開機自啟"
|
|
||||||
},
|
|
||||||
"badges": {
|
"badges": {
|
||||||
"adminMode": "管理員模式",
|
"adminMode": "管理員模式",
|
||||||
"serviceMode": "服務模式",
|
"serviceMode": "服務模式",
|
||||||
|
|||||||
@ -15,7 +15,6 @@
|
|||||||
"systemProxy": "系統代理"
|
"systemProxy": "系統代理"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"autoLaunchAdmin": "管理員模式可能不支援開機自啟",
|
|
||||||
"silentStart": "程序啟動時以後台模式執行,不顯示程序面板"
|
"silentStart": "程序啟動時以後台模式執行,不顯示程序面板"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
|
|||||||
@ -59,7 +59,6 @@ export const translationKeys = [
|
|||||||
"home.components.systemInfo.fields.lastCheckUpdate",
|
"home.components.systemInfo.fields.lastCheckUpdate",
|
||||||
"home.components.systemInfo.fields.vergeVersion",
|
"home.components.systemInfo.fields.vergeVersion",
|
||||||
"home.components.systemInfo.actions.settings",
|
"home.components.systemInfo.actions.settings",
|
||||||
"home.components.systemInfo.tooltips.autoLaunchAdmin",
|
|
||||||
"home.components.systemInfo.badges.adminMode",
|
"home.components.systemInfo.badges.adminMode",
|
||||||
"home.components.systemInfo.badges.serviceMode",
|
"home.components.systemInfo.badges.serviceMode",
|
||||||
"home.components.systemInfo.badges.sidecarMode",
|
"home.components.systemInfo.badges.sidecarMode",
|
||||||
@ -320,7 +319,6 @@ export const translationKeys = [
|
|||||||
"settings.sections.system.title",
|
"settings.sections.system.title",
|
||||||
"settings.sections.system.toggles.tunMode",
|
"settings.sections.system.toggles.tunMode",
|
||||||
"settings.sections.system.toggles.systemProxy",
|
"settings.sections.system.toggles.systemProxy",
|
||||||
"settings.sections.system.tooltips.autoLaunchAdmin",
|
|
||||||
"settings.sections.system.tooltips.silentStart",
|
"settings.sections.system.tooltips.silentStart",
|
||||||
"settings.sections.system.fields.autoLaunch",
|
"settings.sections.system.fields.autoLaunch",
|
||||||
"settings.sections.system.fields.silentStart",
|
"settings.sections.system.fields.silentStart",
|
||||||
|
|||||||
@ -124,9 +124,6 @@ export interface TranslationResources {
|
|||||||
vergeVersion: string;
|
vergeVersion: string;
|
||||||
};
|
};
|
||||||
title: string;
|
title: string;
|
||||||
tooltips: {
|
|
||||||
autoLaunchAdmin: string;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
tests: {
|
tests: {
|
||||||
title: string;
|
title: string;
|
||||||
@ -1166,7 +1163,6 @@ export interface TranslationResources {
|
|||||||
tunMode: string;
|
tunMode: string;
|
||||||
};
|
};
|
||||||
tooltips: {
|
tooltips: {
|
||||||
autoLaunchAdmin: string;
|
|
||||||
silentStart: string;
|
silentStart: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user