diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs index 33de5e6..3c721a5 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -175,6 +175,15 @@ pub async fn restart_sidecar() -> CmdResult { wrap_err!(CoreManager::global().run_core().await) } +#[tauri::command] +pub fn grant_permission(core: String) -> CmdResult { + #[cfg(target_os = "macos")] + return wrap_err!(manager::grant_permission(core)); + + #[cfg(not(target_os = "macos"))] + return Err("Unsupported target"); +} + /// get the system proxy #[tauri::command] pub fn get_sys_proxy() -> CmdResult { diff --git a/src-tauri/src/core/manager.rs b/src-tauri/src/core/manager.rs new file mode 100644 index 0000000..250dc5c --- /dev/null +++ b/src-tauri/src/core/manager.rs @@ -0,0 +1,37 @@ +/// 给clash内核的tun模式授权 +#[cfg(any(target_os = "macos", target_os = "linux"))] +pub fn grant_permission(core: String) -> anyhow::Result<()> { + use std::process::Command; + use tauri::utils::platform::current_exe; + + let path = current_exe()?.with_file_name(core).canonicalize()?; + let path = path.display(); + + log::debug!("grant_permission path: {path}"); + + #[cfg(target_os = "macos")] + let output = { + let shell = format!("chown root:admin {path}\nchmod +sx {path}"); + let command = format!(r#"do shell script "{shell}" with administrator privileges"#); + Command::new("osascript") + .args(vec!["-e", &command]) + .output()? + }; + + #[cfg(target_os = "linux")] + let output = { + let shell = format!("setcap cap_net_bind_service,cap_net_admin=+ep {path}"); + Command::new("sudo") + .arg("sh") + .arg("-c") + .arg(shell) + .output()? + }; + + if output.status.success() { + Ok(()) + } else { + let stderr = std::str::from_utf8(&output.stderr).unwrap_or(""); + anyhow::bail!("{stderr}"); + } +} diff --git a/src-tauri/src/core/mod.rs b/src-tauri/src/core/mod.rs index 18655ab..4221721 100644 --- a/src-tauri/src/core/mod.rs +++ b/src-tauri/src/core/mod.rs @@ -3,6 +3,7 @@ mod core; pub mod handle; pub mod hotkey; pub mod logger; +pub mod manager; pub mod sysopt; pub mod timer; pub mod tray; diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index a3ff866..6d0f79e 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -36,6 +36,7 @@ fn main() -> std::io::Result<()> { cmds::open_core_dir, // cmds::kill_sidecar, cmds::restart_sidecar, + cmds::grant_permission, // clash cmds::get_clash_info, cmds::get_clash_logs, diff --git a/src/components/setting/mods/clash-core-viewer.tsx b/src/components/setting/mods/clash-core-viewer.tsx new file mode 100644 index 0000000..8d05898 --- /dev/null +++ b/src/components/setting/mods/clash-core-viewer.tsx @@ -0,0 +1,106 @@ +import { mutate } from "swr"; +import { forwardRef, useImperativeHandle, useState } from "react"; +import { BaseDialog, DialogRef, Notice } from "@/components/base"; +import { useTranslation } from "react-i18next"; +import { useVerge } from "@/hooks/use-verge"; +import { useLockFn } from "ahooks"; +import { Lock } from "@mui/icons-material"; +import { IconButton, List, ListItemButton, ListItemText } from "@mui/material"; +import { changeClashCore } from "@/services/cmds"; +import { closeAllConnections } from "@/services/api"; +import { grantPermission } from "@/services/cmds"; +import getSystem from "@/utils/get-system"; + +const VALID_CORE = [ + { name: "Clash", core: "clash" }, + { name: "Clash Meta", core: "clash-meta" }, +]; + +const OS = getSystem(); + +export const ClashCoreViewer = forwardRef((props, ref) => { + const { t } = useTranslation(); + + const { verge, mutateVerge } = useVerge(); + + const [open, setOpen] = useState(false); + + useImperativeHandle(ref, () => ({ + open: () => setOpen(true), + close: () => setOpen(false), + })); + + const { clash_core = "clash" } = verge ?? {}; + + const onCoreChange = useLockFn(async (core: string) => { + if (core === clash_core) return; + + try { + closeAllConnections(); + await changeClashCore(core); + mutateVerge(); + setTimeout(() => { + mutate("getClashConfig"); + mutate("getVersion"); + }, 100); + Notice.success(`Successfully switch to ${core}`, 1000); + } catch (err: any) { + Notice.error(err?.message || err.toString()); + } + }); + + const onGrant = useLockFn(async (core: string) => { + try { + await grantPermission(core); + Notice.success(`Successfully grant permission to ${core}`, 1000); + } catch (err: any) { + Notice.error(err?.message || err.toString()); + } + }); + + return ( + setOpen(false)} + onCancel={() => setOpen(false)} + > + + {VALID_CORE.map((each) => ( + onCoreChange(each.core)} + > + + + {(OS === "macos" || OS === "linux") && ( + { + e.preventDefault(); + e.stopPropagation(); + onGrant(each.core); + }} + > + + + )} + + ))} + + + ); +}); diff --git a/src/components/setting/setting-clash.tsx b/src/components/setting/setting-clash.tsx index a3af397..19f77ca 100644 --- a/src/components/setting/setting-clash.tsx +++ b/src/components/setting/setting-clash.tsx @@ -8,16 +8,16 @@ import { Typography, IconButton, } from "@mui/material"; -import { ArrowForward } from "@mui/icons-material"; +import { ArrowForward, Settings } from "@mui/icons-material"; import { DialogRef } from "@/components/base"; import { useClash } from "@/hooks/use-clash"; import { GuardState } from "./mods/guard-state"; -import { CoreSwitch } from "./mods/core-switch"; import { WebUIViewer } from "./mods/web-ui-viewer"; import { ClashFieldViewer } from "./mods/clash-field-viewer"; import { ClashPortViewer } from "./mods/clash-port-viewer"; import { ControllerViewer } from "./mods/controller-viewer"; import { SettingList, SettingItem } from "./mods/setting-comp"; +import { ClashCoreViewer } from "./mods/clash-core-viewer"; interface Props { onError: (err: Error) => void; @@ -39,6 +39,7 @@ const SettingClash = ({ onError }: Props) => { const fieldRef = useRef(null); const portRef = useRef(null); const ctrlRef = useRef(null); + const coreRef = useRef(null); const onSwitchFormat = (_e: any, value: boolean) => value; const onChangeData = (patch: Partial) => { @@ -51,6 +52,7 @@ const SettingClash = ({ onError }: Props) => { + { - }> + coreRef.current?.open()} + > + + + } + > {version} diff --git a/src/services/cmds.ts b/src/services/cmds.ts index d6bbca5..2bd857e 100644 --- a/src/services/cmds.ts +++ b/src/services/cmds.ts @@ -127,6 +127,10 @@ export async function restartSidecar() { return invoke("restart_sidecar"); } +export async function grantPermission(core: string) { + return invoke("grant_permission", { core }); +} + export async function openAppDir() { return invoke("open_app_dir").catch((err) => Notice.error(err?.message || err.toString(), 1500)