mirror of
https://github.com/clash-verge-rev/clash-verge-rev
synced 2025-05-05 07:13:44 +08:00
feat: Modify startup logic and install services by default
This commit is contained in:
parent
35f5e4ca41
commit
9b2b447b8b
@ -383,16 +383,6 @@ pub mod service {
|
|||||||
pub async fn check_service() -> CmdResult<service::JsonResponse> {
|
pub async fn check_service() -> CmdResult<service::JsonResponse> {
|
||||||
wrap_err!(service::check_service().await)
|
wrap_err!(service::check_service().await)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn install_service(passwd: String) -> CmdResult {
|
|
||||||
wrap_err!(service::install_service(passwd).await)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn uninstall_service(passwd: String) -> CmdResult {
|
|
||||||
wrap_err!(service::uninstall_service(passwd).await)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
|
@ -56,10 +56,6 @@ pub struct IVerge {
|
|||||||
/// clash tun mode
|
/// clash tun mode
|
||||||
pub enable_tun_mode: Option<bool>,
|
pub enable_tun_mode: Option<bool>,
|
||||||
|
|
||||||
/// windows service mode
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub enable_service_mode: Option<bool>,
|
|
||||||
|
|
||||||
/// can the app auto startup
|
/// can the app auto startup
|
||||||
pub enable_auto_launch: Option<bool>,
|
pub enable_auto_launch: Option<bool>,
|
||||||
|
|
||||||
@ -279,7 +275,6 @@ impl IVerge {
|
|||||||
patch!(tun_tray_icon);
|
patch!(tun_tray_icon);
|
||||||
|
|
||||||
patch!(enable_tun_mode);
|
patch!(enable_tun_mode);
|
||||||
patch!(enable_service_mode);
|
|
||||||
patch!(enable_auto_launch);
|
patch!(enable_auto_launch);
|
||||||
patch!(enable_silent_start);
|
patch!(enable_silent_start);
|
||||||
patch!(enable_random_port);
|
patch!(enable_random_port);
|
||||||
|
@ -6,7 +6,6 @@ use anyhow::{bail, Result};
|
|||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use serde_yaml::Mapping;
|
use serde_yaml::Mapping;
|
||||||
use std::{sync::Arc, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
use tauri_plugin_shell::process::{CommandChild, CommandEvent};
|
|
||||||
use tauri_plugin_shell::ShellExt;
|
use tauri_plugin_shell::ShellExt;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
@ -14,7 +13,6 @@ use tokio::time::sleep;
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CoreManager {
|
pub struct CoreManager {
|
||||||
running: Arc<Mutex<bool>>,
|
running: Arc<Mutex<bool>>,
|
||||||
sidecar: Arc<Mutex<Option<CommandChild>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CoreManager {
|
impl CoreManager {
|
||||||
@ -22,7 +20,6 @@ impl CoreManager {
|
|||||||
static CORE_MANAGER: OnceCell<CoreManager> = OnceCell::new();
|
static CORE_MANAGER: OnceCell<CoreManager> = OnceCell::new();
|
||||||
CORE_MANAGER.get_or_init(|| CoreManager {
|
CORE_MANAGER.get_or_init(|| CoreManager {
|
||||||
running: Arc::new(Mutex::new(false)),
|
running: Arc::new(Mutex::new(false)),
|
||||||
sidecar: Arc::new(Mutex::new(None)),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,6 +72,7 @@ impl CoreManager {
|
|||||||
log::debug!("core is not running");
|
log::debug!("core is not running");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
println!("stop core");
|
||||||
|
|
||||||
// 关闭tun模式
|
// 关闭tun模式
|
||||||
let mut disable = Mapping::new();
|
let mut disable = Mapping::new();
|
||||||
@ -84,22 +82,18 @@ impl CoreManager {
|
|||||||
log::debug!(target: "app", "disable tun mode");
|
log::debug!(target: "app", "disable tun mode");
|
||||||
log_err!(clash_api::patch_configs(&disable).await);
|
log_err!(clash_api::patch_configs(&disable).await);
|
||||||
|
|
||||||
if let Some(sidecar) = self.sidecar.lock().await.take() {
|
// 服务模式
|
||||||
let _ = sidecar.kill();
|
if service::check_service().await.is_ok() {
|
||||||
} else {
|
log::debug!(target: "app", "stop the core by service");
|
||||||
// 服务模式
|
service::stop_core_by_service().await?;
|
||||||
if service::check_service().await.is_ok() {
|
|
||||||
log::debug!(target: "app", "stop the core by service");
|
|
||||||
log_err!(service::stop_core_by_service().await);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
*running = false;
|
*running = false;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 启动核心
|
/// 启动核心
|
||||||
pub async fn start_core(&self) -> Result<()> {
|
pub async fn start_core(&self) -> Result<()> {
|
||||||
|
println!("start core");
|
||||||
let mut running = self.running.lock().await;
|
let mut running = self.running.lock().await;
|
||||||
if *running {
|
if *running {
|
||||||
log::debug!("core is running");
|
log::debug!("core is running");
|
||||||
@ -107,62 +101,13 @@ impl CoreManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let config_path = Config::generate_file(ConfigType::Run)?;
|
let config_path = Config::generate_file(ConfigType::Run)?;
|
||||||
let clash_core = { Config::verge().latest().clash_core.clone() };
|
|
||||||
let clash_core = clash_core.unwrap_or("verge-mihomo".into());
|
|
||||||
// 服务模式
|
|
||||||
let service_enable = { Config::verge().latest().enable_service_mode };
|
|
||||||
let service_enable = service_enable.unwrap_or(false);
|
|
||||||
|
|
||||||
|
// 服务模式
|
||||||
if service::check_service().await.is_ok() {
|
if service::check_service().await.is_ok() {
|
||||||
log::debug!(target: "app", "try to run core in service mode");
|
log::debug!(target: "app", "try to run core in service mode");
|
||||||
if service_enable {
|
service::run_core_by_service(&config_path).await?;
|
||||||
service::run_core_by_service(&config_path).await?;
|
*running = true;
|
||||||
let mut sidecar = self.sidecar.lock().await;
|
|
||||||
if sidecar.is_some() {
|
|
||||||
sidecar.take();
|
|
||||||
}
|
|
||||||
*running = true;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let app_dir = dirs::app_home_dir()?;
|
|
||||||
let app_dir = dirs::path_to_str(&app_dir)?;
|
|
||||||
let config_path = dirs::path_to_str(&config_path)?;
|
|
||||||
let args = vec!["-d", app_dir, "-f", config_path];
|
|
||||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
|
||||||
let cmd = app_handle.shell().sidecar(clash_core)?;
|
|
||||||
let (mut rx, cmd_child) = cmd.args(args).spawn()?;
|
|
||||||
let mut sidecar = self.sidecar.lock().await;
|
|
||||||
|
|
||||||
*sidecar = Some(cmd_child);
|
|
||||||
|
|
||||||
tauri::async_runtime::spawn(async move {
|
|
||||||
while let Some(event) = rx.recv().await {
|
|
||||||
match event {
|
|
||||||
CommandEvent::Stdout(line) => {
|
|
||||||
let line = String::from_utf8(line).unwrap_or_default();
|
|
||||||
log::info!(target: "app", "[mihomo]: {line}");
|
|
||||||
Logger::global().set_log(line);
|
|
||||||
}
|
|
||||||
CommandEvent::Stderr(err) => {
|
|
||||||
let err = String::from_utf8(err).unwrap_or_default();
|
|
||||||
log::error!(target: "app", "[mihomo]: {err}");
|
|
||||||
Logger::global().set_log(err);
|
|
||||||
}
|
|
||||||
CommandEvent::Error(err) => {
|
|
||||||
log::error!(target: "app", "[mihomo]: {err}");
|
|
||||||
Logger::global().set_log(err);
|
|
||||||
}
|
|
||||||
CommandEvent::Terminated(_) => {
|
|
||||||
log::info!(target: "app", "mihomo core terminated");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
*running = true;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,32 +26,42 @@ pub struct JsonResponse {
|
|||||||
pub data: Option<ResponseBody>,
|
pub data: Option<ResponseBody>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
pub fn sudo(passwd: &String, cmd: String) -> StdCommand {
|
|
||||||
let shell = format!("echo \"{}\" | sudo -S {}", passwd, cmd);
|
|
||||||
let mut command = StdCommand::new("bash");
|
|
||||||
command.arg("-c").arg(shell);
|
|
||||||
command
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Install the Clash Verge Service
|
/// Install the Clash Verge Service
|
||||||
/// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程
|
/// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程
|
||||||
///
|
///
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub async fn install_service(_passwd: String) -> Result<()> {
|
pub async fn reinstall_service(_passwd: String) -> Result<()> {
|
||||||
use deelevate::{PrivilegeLevel, Token};
|
use deelevate::{PrivilegeLevel, Token};
|
||||||
use runas::Command as RunasCommand;
|
use runas::Command as RunasCommand;
|
||||||
use std::os::windows::process::CommandExt;
|
use std::os::windows::process::CommandExt;
|
||||||
|
|
||||||
let binary_path = dirs::service_path()?;
|
let binary_path = dirs::service_path()?;
|
||||||
let install_path = binary_path.with_file_name("install-service.exe");
|
let install_path = binary_path.with_file_name("install-service.exe");
|
||||||
|
let uninstall_path = binary_path.with_file_name("uninstall-service.exe");
|
||||||
|
|
||||||
if !install_path.exists() {
|
if !install_path.exists() {
|
||||||
bail!("installer exe not found");
|
bail!("installer exe not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !uninstall_path.exists() {
|
||||||
|
bail!("uninstaller exe not found");
|
||||||
|
}
|
||||||
|
|
||||||
let token = Token::with_current_process()?;
|
let token = Token::with_current_process()?;
|
||||||
let level = token.privilege_level()?;
|
let level = token.privilege_level()?;
|
||||||
|
let status = match level {
|
||||||
|
PrivilegeLevel::NotPrivileged => RunasCommand::new(uninstall_path).show(false).status()?,
|
||||||
|
_ => StdCommand::new(uninstall_path)
|
||||||
|
.creation_flags(0x08000000)
|
||||||
|
.status()?,
|
||||||
|
};
|
||||||
|
|
||||||
|
if !status.success() {
|
||||||
|
bail!(
|
||||||
|
"failed to uninstall service with status {}",
|
||||||
|
status.code().unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let status = match level {
|
let status = match level {
|
||||||
PrivilegeLevel::NotPrivileged => RunasCommand::new(install_path).show(false).status()?,
|
PrivilegeLevel::NotPrivileged => RunasCommand::new(install_path).show(false).status()?,
|
||||||
@ -71,45 +81,52 @@ pub async fn install_service(_passwd: String) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub async fn install_service(passwd: String) -> Result<()> {
|
pub async fn reinstall_service(passwd: String) -> Result<()> {
|
||||||
use users::get_effective_uid;
|
use users::get_effective_uid;
|
||||||
|
|
||||||
let binary_path = dirs::service_path()?;
|
let binary_path = dirs::service_path()?;
|
||||||
let installer_path = binary_path.with_file_name("install-service");
|
let installer_path = binary_path.with_file_name("install-service");
|
||||||
|
let uninstaller_path = binary_path.with_file_name("uninstall-service");
|
||||||
|
|
||||||
if !installer_path.exists() {
|
if !installer_path.exists() {
|
||||||
bail!("installer not found");
|
bail!("installer not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
let output = match get_effective_uid() {
|
if !uninstaller_path.exists() {
|
||||||
0 => {
|
bail!("uninstaller not found");
|
||||||
StdCommand::new("chmod")
|
}
|
||||||
.arg("+x")
|
|
||||||
.arg(installer_path.clone())
|
let elevator = crate::utils::help::linux_elevator();
|
||||||
.output()?;
|
let status = match get_effective_uid() {
|
||||||
StdCommand::new("chmod")
|
0 => StdCommand::new(uninstaller_path).status()?,
|
||||||
.arg("+x")
|
_ => StdCommand::new(elevator)
|
||||||
.arg(binary_path)
|
.arg("sh")
|
||||||
.output()?;
|
.arg("-c")
|
||||||
StdCommand::new(installer_path.clone()).output()?
|
.arg(uninstaller_path)
|
||||||
}
|
.status()?,
|
||||||
_ => {
|
|
||||||
sudo(
|
|
||||||
&passwd,
|
|
||||||
format!("chmod +x {}", installer_path.to_string_lossy()),
|
|
||||||
)
|
|
||||||
.output()?;
|
|
||||||
sudo(
|
|
||||||
&passwd,
|
|
||||||
format!("chmod +x {}", binary_path.to_string_lossy()),
|
|
||||||
)
|
|
||||||
.output()?;
|
|
||||||
sudo(&passwd, format!("{}", installer_path.to_string_lossy())).output()?
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
if !output.status.success() {
|
|
||||||
|
if !status.success() {
|
||||||
bail!(
|
bail!(
|
||||||
"failed to install service with error: {}",
|
"failed to install service with status {}",
|
||||||
String::from_utf8_lossy(&output.stderr)
|
status.code().unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let elevator = crate::utils::help::linux_elevator();
|
||||||
|
let status = match get_effective_uid() {
|
||||||
|
0 => StdCommand::new(installer_path).status()?,
|
||||||
|
_ => StdCommand::new(elevator)
|
||||||
|
.arg("sh")
|
||||||
|
.arg("-c")
|
||||||
|
.arg(installer_path)
|
||||||
|
.status()?,
|
||||||
|
};
|
||||||
|
|
||||||
|
if !status.success() {
|
||||||
|
bail!(
|
||||||
|
"failed to install service with status {}",
|
||||||
|
status.code().unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,38 +134,35 @@ pub async fn install_service(passwd: String) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub async fn install_service(passwd: String) -> Result<()> {
|
pub async fn reinstall_service() -> Result<()> {
|
||||||
let binary_path = dirs::service_path()?;
|
let binary_path = dirs::service_path()?;
|
||||||
let installer_path = binary_path.with_file_name("install-service");
|
let installer_path = binary_path.with_file_name("install-service");
|
||||||
|
let uninstall_path = binary_path.with_file_name("uninstall-service");
|
||||||
|
|
||||||
if !installer_path.exists() {
|
if !installer_path.exists() {
|
||||||
bail!("installer not found");
|
bail!("installer not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
sudo(
|
if !uninstall_path.exists() {
|
||||||
&passwd,
|
bail!("uninstaller not found");
|
||||||
format!(
|
|
||||||
"chmod +x {}",
|
|
||||||
installer_path.to_string_lossy().replace(" ", "\\ ")
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.output()?;
|
|
||||||
let output = sudo(
|
|
||||||
&passwd,
|
|
||||||
installer_path
|
|
||||||
.to_string_lossy()
|
|
||||||
.replace(" ", "\\ ")
|
|
||||||
.to_string(),
|
|
||||||
)
|
|
||||||
.output()?;
|
|
||||||
|
|
||||||
if !output.status.success() {
|
|
||||||
bail!(
|
|
||||||
"failed to install service with error: {}",
|
|
||||||
String::from_utf8_lossy(&output.stderr)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let install_shell: String = installer_path.to_string_lossy().replace(" ", "\\\\ ");
|
||||||
|
let uninstall_shell: String = uninstall_path.to_string_lossy().replace(" ", "\\\\ ");
|
||||||
|
let command = format!(
|
||||||
|
r#"do shell script "{uninstall_shell} && {install_shell}" with administrator privileges"#
|
||||||
|
);
|
||||||
|
|
||||||
|
let status = StdCommand::new("osascript")
|
||||||
|
.args(vec!["-e", &command])
|
||||||
|
.status()?;
|
||||||
|
|
||||||
|
if !status.success() {
|
||||||
|
bail!(
|
||||||
|
"failed to install service with status {}",
|
||||||
|
status.code().unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
/// Uninstall the Clash Verge Service
|
/// Uninstall the Clash Verge Service
|
||||||
@ -186,82 +200,6 @@ pub async fn uninstall_service(_passwd: String) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
pub async fn uninstall_service(passwd: String) -> Result<()> {
|
|
||||||
use users::get_effective_uid;
|
|
||||||
|
|
||||||
let binary_path = dirs::service_path()?;
|
|
||||||
let uninstaller_path = binary_path.with_file_name("uninstall-service");
|
|
||||||
|
|
||||||
if !uninstaller_path.exists() {
|
|
||||||
bail!("uninstaller not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
let output = match get_effective_uid() {
|
|
||||||
0 => {
|
|
||||||
StdCommand::new("chmod")
|
|
||||||
.arg("+x")
|
|
||||||
.arg(uninstaller_path.clone())
|
|
||||||
.output()?;
|
|
||||||
StdCommand::new(uninstaller_path.clone()).output()?
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
sudo(
|
|
||||||
&passwd,
|
|
||||||
format!("chmod +x {}", uninstaller_path.to_string_lossy()),
|
|
||||||
)
|
|
||||||
.output()?;
|
|
||||||
|
|
||||||
sudo(&passwd, format!("{}", uninstaller_path.to_string_lossy())).output()?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if !output.status.success() {
|
|
||||||
bail!(
|
|
||||||
"failed to install service with error: {}",
|
|
||||||
String::from_utf8_lossy(&output.stderr)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
pub async fn uninstall_service(passwd: String) -> Result<()> {
|
|
||||||
let binary_path = dirs::service_path()?;
|
|
||||||
let uninstaller_path = binary_path.with_file_name("uninstall-service");
|
|
||||||
|
|
||||||
if !uninstaller_path.exists() {
|
|
||||||
bail!("uninstaller not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
sudo(
|
|
||||||
&passwd,
|
|
||||||
format!(
|
|
||||||
"chmod +x {}",
|
|
||||||
uninstaller_path.to_string_lossy().replace(" ", "\\ ")
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.output()?;
|
|
||||||
let output = sudo(
|
|
||||||
&passwd,
|
|
||||||
uninstaller_path
|
|
||||||
.to_string_lossy()
|
|
||||||
.replace(" ", "\\ ")
|
|
||||||
.to_string(),
|
|
||||||
)
|
|
||||||
.output()?;
|
|
||||||
|
|
||||||
if !output.status.success() {
|
|
||||||
bail!(
|
|
||||||
"failed to uninstall service with error: {}",
|
|
||||||
String::from_utf8_lossy(&output.stderr)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// check the windows service status
|
/// check the windows service status
|
||||||
pub async fn check_service() -> Result<JsonResponse> {
|
pub async fn check_service() -> Result<JsonResponse> {
|
||||||
let url = format!("{SERVICE_URL}/get_clash");
|
let url = format!("{SERVICE_URL}/get_clash");
|
||||||
|
@ -364,52 +364,25 @@ fn create_tray_menu(
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let separator = &PredefinedMenuItem::separator(app_handle).unwrap();
|
let separator = &PredefinedMenuItem::separator(app_handle).unwrap();
|
||||||
let enable = {
|
|
||||||
Config::verge()
|
|
||||||
.latest()
|
|
||||||
.enable_service_mode
|
|
||||||
.unwrap_or(false)
|
|
||||||
};
|
|
||||||
|
|
||||||
let menu = if enable {
|
|
||||||
tauri::menu::MenuBuilder::new(app_handle)
|
|
||||||
.items(&[
|
|
||||||
open_window,
|
|
||||||
separator,
|
|
||||||
rule_mode,
|
|
||||||
global_mode,
|
|
||||||
direct_mode,
|
|
||||||
separator,
|
|
||||||
system_proxy,
|
|
||||||
tun_mode,
|
|
||||||
copy_env,
|
|
||||||
open_dir,
|
|
||||||
more,
|
|
||||||
separator,
|
|
||||||
quit,
|
|
||||||
])
|
|
||||||
.build()
|
|
||||||
.unwrap()
|
|
||||||
} else {
|
|
||||||
tauri::menu::MenuBuilder::new(app_handle)
|
|
||||||
.items(&[
|
|
||||||
open_window,
|
|
||||||
separator,
|
|
||||||
rule_mode,
|
|
||||||
global_mode,
|
|
||||||
direct_mode,
|
|
||||||
separator,
|
|
||||||
system_proxy,
|
|
||||||
copy_env,
|
|
||||||
open_dir,
|
|
||||||
more,
|
|
||||||
separator,
|
|
||||||
quit,
|
|
||||||
])
|
|
||||||
.build()
|
|
||||||
.unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
|
let menu = tauri::menu::MenuBuilder::new(app_handle)
|
||||||
|
.items(&[
|
||||||
|
open_window,
|
||||||
|
separator,
|
||||||
|
rule_mode,
|
||||||
|
global_mode,
|
||||||
|
direct_mode,
|
||||||
|
separator,
|
||||||
|
system_proxy,
|
||||||
|
tun_mode,
|
||||||
|
copy_env,
|
||||||
|
open_dir,
|
||||||
|
more,
|
||||||
|
separator,
|
||||||
|
quit,
|
||||||
|
])
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
Ok(menu)
|
Ok(menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,11 +177,6 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> {
|
|||||||
let mut should_update_sysproxy = false;
|
let mut should_update_sysproxy = false;
|
||||||
let mut should_update_systray_part = false;
|
let mut should_update_systray_part = false;
|
||||||
|
|
||||||
let service_mode = patch.enable_service_mode;
|
|
||||||
if service_mode.is_some() {
|
|
||||||
should_restart_core = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if tun_mode.is_some() {
|
if tun_mode.is_some() {
|
||||||
should_update_clash_config = true;
|
should_update_clash_config = true;
|
||||||
}
|
}
|
||||||
|
@ -118,8 +118,6 @@ pub fn run() {
|
|||||||
cmds::save_profile_file,
|
cmds::save_profile_file,
|
||||||
// service mode
|
// service mode
|
||||||
cmds::service::check_service,
|
cmds::service::check_service,
|
||||||
cmds::service::install_service,
|
|
||||||
cmds::service::uninstall_service,
|
|
||||||
// clash api
|
// clash api
|
||||||
cmds::clash_api_get_proxy_delay
|
cmds::clash_api_get_proxy_delay
|
||||||
]);
|
]);
|
||||||
|
@ -105,6 +105,21 @@ pub fn open_file(app: tauri::AppHandle, path: PathBuf) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub fn linux_elevator() -> &'static str {
|
||||||
|
use std::process::Command;
|
||||||
|
match Command::new("which").arg("pkexec").output() {
|
||||||
|
Ok(output) => {
|
||||||
|
if output.stdout.is_empty() {
|
||||||
|
"sudo"
|
||||||
|
} else {
|
||||||
|
"pkexec"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => "sudo",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! error {
|
macro_rules! error {
|
||||||
($result: expr) => {
|
($result: expr) => {
|
||||||
|
@ -42,6 +42,13 @@ pub async fn resolve_setup(app: &mut App) {
|
|||||||
|
|
||||||
handle::Handle::global().init(app.app_handle());
|
handle::Handle::global().init(app.app_handle());
|
||||||
VERSION.get_or_init(|| version.clone());
|
VERSION.get_or_init(|| version.clone());
|
||||||
|
|
||||||
|
if service::check_service().await.is_err() {
|
||||||
|
log_err!(service::reinstall_service().await);
|
||||||
|
//延迟启动,避免闪屏
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(1000));
|
||||||
|
}
|
||||||
|
|
||||||
log_err!(init::init_config());
|
log_err!(init::init_config());
|
||||||
log_err!(init::init_resources());
|
log_err!(init::init_resources());
|
||||||
log_err!(init::init_scheme());
|
log_err!(init::init_scheme());
|
||||||
|
@ -1,134 +0,0 @@
|
|||||||
import { KeyedMutator } from "swr";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useLockFn } from "ahooks";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { installService, uninstallService } from "@/services/cmds";
|
|
||||||
import { Notice } from "@/components/base";
|
|
||||||
import { LoadingButton } from "@mui/lab";
|
|
||||||
import { PasswordInput } from "./password-input";
|
|
||||||
import getSystem from "@/utils/get-system";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
status: "active" | "installed" | "unknown" | "uninstall";
|
|
||||||
mutate: KeyedMutator<"active" | "installed" | "unknown" | "uninstall">;
|
|
||||||
patchVerge: (value: Partial<IVergeConfig>) => Promise<void>;
|
|
||||||
onChangeData: (patch: Partial<IVergeConfig>) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ServiceSwitcher = (props: Props) => {
|
|
||||||
const { status, mutate, patchVerge, onChangeData } = props;
|
|
||||||
const isWindows = getSystem() === "windows";
|
|
||||||
const isActive = status === "active";
|
|
||||||
const isInstalled = status === "installed";
|
|
||||||
const isUninstall = status === "uninstall" || status === "unknown";
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [serviceLoading, setServiceLoading] = useState(false);
|
|
||||||
const [uninstallServiceLoaing, setUninstallServiceLoading] = useState(false);
|
|
||||||
const [openInstall, setOpenInstall] = useState(false);
|
|
||||||
const [openUninstall, setOpenUninstall] = useState(false);
|
|
||||||
|
|
||||||
async function install(passwd: string) {
|
|
||||||
try {
|
|
||||||
setOpenInstall(false);
|
|
||||||
await installService(passwd);
|
|
||||||
await mutate();
|
|
||||||
setTimeout(() => {
|
|
||||||
mutate();
|
|
||||||
}, 2000);
|
|
||||||
Notice.success(t("Service Installed Successfully"));
|
|
||||||
setServiceLoading(false);
|
|
||||||
} catch (err: any) {
|
|
||||||
await mutate();
|
|
||||||
setTimeout(() => {
|
|
||||||
mutate();
|
|
||||||
}, 2000);
|
|
||||||
Notice.error(err.message || err.toString());
|
|
||||||
setServiceLoading(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function uninstall(passwd: string) {
|
|
||||||
try {
|
|
||||||
setOpenUninstall(false);
|
|
||||||
await uninstallService(passwd);
|
|
||||||
await mutate();
|
|
||||||
setTimeout(() => {
|
|
||||||
mutate();
|
|
||||||
}, 2000);
|
|
||||||
Notice.success(t("Service Uninstalled Successfully"));
|
|
||||||
setUninstallServiceLoading(false);
|
|
||||||
} catch (err: any) {
|
|
||||||
await mutate();
|
|
||||||
setTimeout(() => {
|
|
||||||
mutate();
|
|
||||||
}, 2000);
|
|
||||||
Notice.error(err.message || err.toString());
|
|
||||||
setUninstallServiceLoading(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onInstallOrEnableService = useLockFn(async () => {
|
|
||||||
setServiceLoading(true);
|
|
||||||
if (isUninstall) {
|
|
||||||
// install service
|
|
||||||
if (isWindows) {
|
|
||||||
await install("");
|
|
||||||
} else {
|
|
||||||
setOpenInstall(true);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
// enable or disable service
|
|
||||||
await patchVerge({ enable_service_mode: !isActive });
|
|
||||||
onChangeData({ enable_service_mode: !isActive });
|
|
||||||
await mutate();
|
|
||||||
setTimeout(() => {
|
|
||||||
mutate();
|
|
||||||
}, 2000);
|
|
||||||
setServiceLoading(false);
|
|
||||||
} catch (err: any) {
|
|
||||||
await mutate();
|
|
||||||
Notice.error(err.message || err.toString());
|
|
||||||
setServiceLoading(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const onUninstallService = useLockFn(async () => {
|
|
||||||
setUninstallServiceLoading(true);
|
|
||||||
if (isWindows) {
|
|
||||||
await uninstall("");
|
|
||||||
} else {
|
|
||||||
setOpenUninstall(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{openInstall && <PasswordInput onConfirm={install} />}
|
|
||||||
{openUninstall && <PasswordInput onConfirm={uninstall} />}
|
|
||||||
|
|
||||||
<LoadingButton
|
|
||||||
size="small"
|
|
||||||
variant={isUninstall ? "outlined" : "contained"}
|
|
||||||
onClick={onInstallOrEnableService}
|
|
||||||
loading={serviceLoading}
|
|
||||||
>
|
|
||||||
{isActive ? t("Disable") : isInstalled ? t("Enable") : t("Install")}
|
|
||||||
</LoadingButton>
|
|
||||||
{isInstalled && (
|
|
||||||
<LoadingButton
|
|
||||||
size="small"
|
|
||||||
variant="outlined"
|
|
||||||
color="error"
|
|
||||||
sx={{ ml: 1 }}
|
|
||||||
onClick={onUninstallService}
|
|
||||||
loading={uninstallServiceLoaing}
|
|
||||||
>
|
|
||||||
{t("Uninstall")}
|
|
||||||
</LoadingButton>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
@ -7,7 +7,6 @@ import { useVerge } from "@/hooks/use-verge";
|
|||||||
import { DialogRef, Notice, Switch } from "@/components/base";
|
import { DialogRef, Notice, Switch } from "@/components/base";
|
||||||
import { SettingList, SettingItem } from "./mods/setting-comp";
|
import { SettingList, SettingItem } from "./mods/setting-comp";
|
||||||
import { GuardState } from "./mods/guard-state";
|
import { GuardState } from "./mods/guard-state";
|
||||||
import { ServiceSwitcher } from "./mods/service-switcher";
|
|
||||||
import { SysproxyViewer } from "./mods/sysproxy-viewer";
|
import { SysproxyViewer } from "./mods/sysproxy-viewer";
|
||||||
import { TunViewer } from "./mods/tun-viewer";
|
import { TunViewer } from "./mods/tun-viewer";
|
||||||
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
||||||
@ -67,37 +66,15 @@ const SettingSystem = ({ onError }: Props) => {
|
|||||||
onCatch={onError}
|
onCatch={onError}
|
||||||
onFormat={onSwitchFormat}
|
onFormat={onSwitchFormat}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
if (serviceStatus !== "active") {
|
onChangeData({ enable_tun_mode: e });
|
||||||
onChangeData({ enable_tun_mode: false });
|
|
||||||
} else {
|
|
||||||
onChangeData({ enable_tun_mode: e });
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
onGuard={(e) => {
|
onGuard={(e) => {
|
||||||
if (serviceStatus !== "active" && e) {
|
return patchVerge({ enable_tun_mode: e });
|
||||||
Notice.error(t("Please Enable Service Mode"));
|
|
||||||
return Promise.resolve();
|
|
||||||
} else {
|
|
||||||
return patchVerge({ enable_tun_mode: e });
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Switch edge="end" />
|
<Switch edge="end" />
|
||||||
</GuardState>
|
</GuardState>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem
|
|
||||||
label={t("Service Mode")}
|
|
||||||
extra={<TooltipIcon title={t("Service Mode Info")} />}
|
|
||||||
>
|
|
||||||
<ServiceSwitcher
|
|
||||||
status={serviceStatus ?? "unknown"}
|
|
||||||
mutate={mutateServiceStatus}
|
|
||||||
patchVerge={patchVerge}
|
|
||||||
onChangeData={onChangeData}
|
|
||||||
/>
|
|
||||||
</SettingItem>
|
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
label={t("System Proxy")}
|
label={t("System Proxy")}
|
||||||
extra={
|
extra={
|
||||||
|
@ -200,15 +200,6 @@ export async function checkService() {
|
|||||||
return "uninstall";
|
return "uninstall";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function installService(passwd: string) {
|
|
||||||
return invoke<void>("install_service", { passwd });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function uninstallService(passwd: string) {
|
|
||||||
return invoke<void>("uninstall_service", { passwd });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function invoke_uwp_tool() {
|
export async function invoke_uwp_tool() {
|
||||||
return invoke<void>("invoke_uwp_tool").catch((err) =>
|
return invoke<void>("invoke_uwp_tool").catch((err) =>
|
||||||
Notice.error(err?.message || err.toString(), 1500)
|
Notice.error(err?.message || err.toString(), 1500)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user