diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs index 55d37f3..d4ca786 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -251,6 +251,11 @@ pub mod service { wrap_err!(crate::core::Service::start_service().await) } + #[tauri::command] + pub async fn stop_service() -> Result<(), String> { + wrap_err!(crate::core::Service::stop_service().await) + } + #[tauri::command] pub async fn check_service() -> Result { wrap_err!(crate::core::Service::check_service().await) @@ -258,11 +263,13 @@ pub mod service { #[tauri::command] pub async fn install_service() -> Result<(), String> { - wrap_err!(crate::core::Service::install_service().await) + wrap_err!(crate::core::Service::install_service().await)?; + wrap_err!(crate::core::Service::start_service().await) } #[tauri::command] pub async fn uninstall_service() -> Result<(), String> { + log_if_err!(crate::core::Service::stop_service().await); wrap_err!(crate::core::Service::uninstall_service().await) } } @@ -276,6 +283,11 @@ pub mod service { Ok(()) } + #[tauri::command] + pub async fn stop_service() -> Result<(), String> { + Ok(()) + } + #[tauri::command] pub async fn check_service() -> Result<(), String> { Ok(()) diff --git a/src-tauri/src/core/service.rs b/src-tauri/src/core/service.rs index 798bee7..62b10ef 100644 --- a/src-tauri/src/core/service.rs +++ b/src-tauri/src/core/service.rs @@ -294,15 +294,32 @@ pub mod win_service { } } + /// stop service + pub async fn stop_service() -> Result<()> { + let url = format!("{SERVICE_URL}/stop_service"); + let res = reqwest::Client::new() + .post(url) + .send() + .await? + .json::() + .await + .context("failed to connect to the Clash Verge Service")?; + + if res.code != 0 { + bail!(res.msg); + } + + Ok(()) + } + /// check the windows service status pub async fn check_service() -> Result { let url = format!("{SERVICE_URL}/get_clash"); let response = reqwest::get(url) - .await - .context("failed to connect to the Clash Verge Service")? + .await? .json::() .await - .context("failed to parse the Clash Verge Service response")?; + .context("failed to connect to the Clash Verge Service")?; Ok(response) } @@ -335,11 +352,10 @@ pub mod win_service { .post(url) .json(&map) .send() - .await - .context("failed to connect to the Clash Verge Service")? + .await? .json::() .await - .context("failed to parse the Clash Verge Service response")?; + .context("failed to connect to the Clash Verge Service")?; if res.code != 0 { bail!(res.msg); @@ -354,11 +370,10 @@ pub mod win_service { let res = reqwest::Client::new() .post(url) .send() - .await - .context("failed to connect to the Clash Verge Service")? + .await? .json::() .await - .context("failed to parse the Clash Verge Service response")?; + .context("failed to connect to the Clash Verge Service")?; if res.code != 0 { bail!(res.msg); diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 11b9b54..8f17feb 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -129,6 +129,7 @@ fn main() -> std::io::Result<()> { cmds::save_profile_file, // service mode cmds::service::start_service, + cmds::service::stop_service, cmds::service::check_service, cmds::service::install_service, cmds::service::uninstall_service, diff --git a/src/components/setting/service-mode.tsx b/src/components/setting/service-mode.tsx new file mode 100644 index 0000000..768af80 --- /dev/null +++ b/src/components/setting/service-mode.tsx @@ -0,0 +1,104 @@ +import useSWR, { useSWRConfig } from "swr"; +import { useLockFn } from "ahooks"; +import { useTranslation } from "react-i18next"; +import { + Button, + Dialog, + DialogContent, + DialogTitle, + Stack, + Typography, +} from "@mui/material"; +import { + checkService, + installService, + uninstallService, + patchVergeConfig, +} from "../../services/cmds"; +import Notice from "../base/base-notice"; +import noop from "../../utils/noop"; + +interface Props { + open: boolean; + enable: boolean; + onClose: () => void; + onError?: (err: Error) => void; +} + +const ServiceMode = (props: Props) => { + const { open, enable, onClose, onError = noop } = props; + + const { t } = useTranslation(); + const { mutate } = useSWRConfig(); + const { data: status } = useSWR("checkService", checkService, { + revalidateIfStale: true, + shouldRetryOnError: false, + }); + + const state = status != null ? status : "pending"; + + const onInstall = useLockFn(async () => { + try { + await installService(); + mutate("checkService"); + Notice.success("Service installed successfully"); + onClose(); + } catch (err: any) { + mutate("checkService"); + onError(err); + } + }); + + const onUninstall = useLockFn(async () => { + try { + if (state === "active" && enable) { + await patchVergeConfig({ enable_service_mode: false }); + } + + await uninstallService(); + Notice.success("Service uninstalled successfully"); + mutate("checkService"); + onClose(); + } catch (err: any) { + mutate("checkService"); + onError(err); + } + }); + + return ( + + {t("Service Mode")} + + + Current State: {state} + + {(state === "unknown" || state === "uninstall") && ( + + Infomation: Please make sure the Clash Verge Service is installed + and enabled + + )} + + + {state === "uninstall" && ( + + )} + + {(state === "active" || state === "installed") && ( + + )} + + + + ); +}; + +export default ServiceMode; diff --git a/src/components/setting/setting-system.tsx b/src/components/setting/setting-system.tsx index be49d64..bd3b993 100644 --- a/src/components/setting/setting-system.tsx +++ b/src/components/setting/setting-system.tsx @@ -1,24 +1,49 @@ import useSWR, { useSWRConfig } from "swr"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; -import { Box, ListItemText, Switch, TextField } from "@mui/material"; -import { getVergeConfig, patchVergeConfig } from "../../services/cmds"; +import { + Box, + IconButton, + ListItemText, + Switch, + TextField, +} from "@mui/material"; +import { ArrowForward, PrivacyTipRounded } from "@mui/icons-material"; +import { + checkService, + getVergeConfig, + patchVergeConfig, +} from "../../services/cmds"; import { SettingList, SettingItem } from "./setting"; import { CmdType } from "../../services/types"; import GuardState from "./guard-state"; +import ServiceMode from "./service-mode"; import SysproxyTooltip from "./sysproxy-tooltip"; +import getSystem from "../../utils/get-system"; interface Props { onError?: (err: Error) => void; } +const isWIN = getSystem() === "windows"; + const SettingSystem = ({ onError }: Props) => { const { t } = useTranslation(); const { mutate } = useSWRConfig(); const { data: vergeConfig } = useSWR("getVergeConfig", getVergeConfig); + // service mode + const [serviceOpen, setServiceOpen] = useState(false); + const { data: serviceStatus } = useSWR( + isWIN ? "checkService" : null, + checkService, + { revalidateIfStale: true, shouldRetryOnError: false } + ); + const { enable_tun_mode, enable_auto_launch, + enable_service_mode, enable_silent_start, enable_system_proxy, system_proxy_bypass, @@ -46,6 +71,56 @@ const SettingSystem = ({ onError }: Props) => { + {isWIN && ( + + + {t("Service Mode")} + + {(serviceStatus === "active" || + serviceStatus === "installed") && ( + setServiceOpen(true)} + /> + )} + + } + /> + + {serviceStatus === "active" || serviceStatus === "installed" ? ( + onChangeData({ enable_service_mode: e })} + onGuard={(e) => patchVergeConfig({ enable_service_mode: e })} + > + + + ) : ( + setServiceOpen(true)} + > + + + )} + + {serviceOpen && ( + setServiceOpen(false)} + /> + )} + + )} + { - {t("System Proxy")} + {t("System Proxy")} } diff --git a/src/locales/en.json b/src/locales/en.json index 7aff3f9..7d1b06b 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -41,6 +41,7 @@ "Mixed Port": "Mixed Port", "Clash core": "Clash core", "Tun Mode": "Tun Mode", + "Service Mode": "Service Mode", "Auto Launch": "Auto Launch", "Silent Start": "Silent Start", "System Proxy": "System Proxy", diff --git a/src/locales/zh.json b/src/locales/zh.json index d691a8f..8894cfb 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -41,6 +41,7 @@ "Mixed Port": "端口设置", "Clash core": "Clash 内核", "Tun Mode": "Tun 模式", + "Service Mode": "服务模式", "Auto Launch": "开机自启", "Silent Start": "静默启动", "System Proxy": "系统代理", diff --git a/src/services/cmds.ts b/src/services/cmds.ts index 28619a6..6ef0645 100644 --- a/src/services/cmds.ts +++ b/src/services/cmds.ts @@ -112,8 +112,19 @@ export async function startService() { return invoke("start_service"); } +export async function stopService() { + return invoke("stop_service"); +} + export async function checkService() { - return invoke("check_service"); + try { + const result = await invoke("check_service"); + if (result?.code === 0) return "active"; + if (result?.code === 400) return "installed"; + return "unknown"; + } catch (err: any) { + return "uninstall"; + } } export async function installService() {