feat: support to grant permission to clash core
This commit is contained in:
parent
e25a455698
commit
54a5007c01
@ -175,6 +175,15 @@ pub async fn restart_sidecar() -> CmdResult {
|
|||||||
wrap_err!(CoreManager::global().run_core().await)
|
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
|
/// get the system proxy
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_sys_proxy() -> CmdResult<Mapping> {
|
pub fn get_sys_proxy() -> CmdResult<Mapping> {
|
||||||
|
37
src-tauri/src/core/manager.rs
Normal file
37
src-tauri/src/core/manager.rs
Normal file
@ -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}");
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ mod core;
|
|||||||
pub mod handle;
|
pub mod handle;
|
||||||
pub mod hotkey;
|
pub mod hotkey;
|
||||||
pub mod logger;
|
pub mod logger;
|
||||||
|
pub mod manager;
|
||||||
pub mod sysopt;
|
pub mod sysopt;
|
||||||
pub mod timer;
|
pub mod timer;
|
||||||
pub mod tray;
|
pub mod tray;
|
||||||
|
@ -36,6 +36,7 @@ fn main() -> std::io::Result<()> {
|
|||||||
cmds::open_core_dir,
|
cmds::open_core_dir,
|
||||||
// cmds::kill_sidecar,
|
// cmds::kill_sidecar,
|
||||||
cmds::restart_sidecar,
|
cmds::restart_sidecar,
|
||||||
|
cmds::grant_permission,
|
||||||
// clash
|
// clash
|
||||||
cmds::get_clash_info,
|
cmds::get_clash_info,
|
||||||
cmds::get_clash_logs,
|
cmds::get_clash_logs,
|
||||||
|
106
src/components/setting/mods/clash-core-viewer.tsx
Normal file
106
src/components/setting/mods/clash-core-viewer.tsx
Normal file
@ -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<DialogRef>((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 (
|
||||||
|
<BaseDialog
|
||||||
|
open={open}
|
||||||
|
title={t("Clash Core")}
|
||||||
|
contentSx={{
|
||||||
|
pb: 0,
|
||||||
|
width: 320,
|
||||||
|
height: 200,
|
||||||
|
overflowY: "auto",
|
||||||
|
userSelect: "text",
|
||||||
|
marginTop: "-8px",
|
||||||
|
}}
|
||||||
|
disableOk
|
||||||
|
cancelBtn={t("Back")}
|
||||||
|
onClose={() => setOpen(false)}
|
||||||
|
onCancel={() => setOpen(false)}
|
||||||
|
>
|
||||||
|
<List component="nav">
|
||||||
|
{VALID_CORE.map((each) => (
|
||||||
|
<ListItemButton
|
||||||
|
key={each.core}
|
||||||
|
selected={each.core === clash_core}
|
||||||
|
onClick={() => onCoreChange(each.core)}
|
||||||
|
>
|
||||||
|
<ListItemText primary={each.name} secondary={`/${each.core}`} />
|
||||||
|
|
||||||
|
{(OS === "macos" || OS === "linux") && (
|
||||||
|
<IconButton
|
||||||
|
color="inherit"
|
||||||
|
size="small"
|
||||||
|
edge="end"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
onGrant(each.core);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Lock fontSize="inherit" />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</ListItemButton>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</BaseDialog>
|
||||||
|
);
|
||||||
|
});
|
@ -8,16 +8,16 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
IconButton,
|
IconButton,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { ArrowForward } from "@mui/icons-material";
|
import { ArrowForward, Settings } from "@mui/icons-material";
|
||||||
import { DialogRef } from "@/components/base";
|
import { DialogRef } from "@/components/base";
|
||||||
import { useClash } from "@/hooks/use-clash";
|
import { useClash } from "@/hooks/use-clash";
|
||||||
import { GuardState } from "./mods/guard-state";
|
import { GuardState } from "./mods/guard-state";
|
||||||
import { CoreSwitch } from "./mods/core-switch";
|
|
||||||
import { WebUIViewer } from "./mods/web-ui-viewer";
|
import { WebUIViewer } from "./mods/web-ui-viewer";
|
||||||
import { ClashFieldViewer } from "./mods/clash-field-viewer";
|
import { ClashFieldViewer } from "./mods/clash-field-viewer";
|
||||||
import { ClashPortViewer } from "./mods/clash-port-viewer";
|
import { ClashPortViewer } from "./mods/clash-port-viewer";
|
||||||
import { ControllerViewer } from "./mods/controller-viewer";
|
import { ControllerViewer } from "./mods/controller-viewer";
|
||||||
import { SettingList, SettingItem } from "./mods/setting-comp";
|
import { SettingList, SettingItem } from "./mods/setting-comp";
|
||||||
|
import { ClashCoreViewer } from "./mods/clash-core-viewer";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onError: (err: Error) => void;
|
onError: (err: Error) => void;
|
||||||
@ -39,6 +39,7 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
const fieldRef = useRef<DialogRef>(null);
|
const fieldRef = useRef<DialogRef>(null);
|
||||||
const portRef = useRef<DialogRef>(null);
|
const portRef = useRef<DialogRef>(null);
|
||||||
const ctrlRef = useRef<DialogRef>(null);
|
const ctrlRef = useRef<DialogRef>(null);
|
||||||
|
const coreRef = useRef<DialogRef>(null);
|
||||||
|
|
||||||
const onSwitchFormat = (_e: any, value: boolean) => value;
|
const onSwitchFormat = (_e: any, value: boolean) => value;
|
||||||
const onChangeData = (patch: Partial<IConfigData>) => {
|
const onChangeData = (patch: Partial<IConfigData>) => {
|
||||||
@ -51,6 +52,7 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
<ClashFieldViewer ref={fieldRef} />
|
<ClashFieldViewer ref={fieldRef} />
|
||||||
<ClashPortViewer ref={portRef} />
|
<ClashPortViewer ref={portRef} />
|
||||||
<ControllerViewer ref={ctrlRef} />
|
<ControllerViewer ref={ctrlRef} />
|
||||||
|
<ClashCoreViewer ref={coreRef} />
|
||||||
|
|
||||||
<SettingItem label={t("Allow Lan")}>
|
<SettingItem label={t("Allow Lan")}>
|
||||||
<GuardState
|
<GuardState
|
||||||
@ -143,7 +145,21 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem label={t("Clash Core")} extra={<CoreSwitch />}>
|
<SettingItem
|
||||||
|
label={t("Clash Core")}
|
||||||
|
extra={
|
||||||
|
<IconButton
|
||||||
|
color="inherit"
|
||||||
|
size="small"
|
||||||
|
onClick={() => coreRef.current?.open()}
|
||||||
|
>
|
||||||
|
<Settings
|
||||||
|
fontSize="inherit"
|
||||||
|
style={{ cursor: "pointer", opacity: 0.75 }}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
}
|
||||||
|
>
|
||||||
<Typography sx={{ py: "7px", pr: 1 }}>{version}</Typography>
|
<Typography sx={{ py: "7px", pr: 1 }}>{version}</Typography>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
</SettingList>
|
</SettingList>
|
||||||
|
@ -127,6 +127,10 @@ export async function restartSidecar() {
|
|||||||
return invoke<void>("restart_sidecar");
|
return invoke<void>("restart_sidecar");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function grantPermission(core: string) {
|
||||||
|
return invoke<void>("grant_permission", { core });
|
||||||
|
}
|
||||||
|
|
||||||
export async function openAppDir() {
|
export async function openAppDir() {
|
||||||
return invoke<void>("open_app_dir").catch((err) =>
|
return invoke<void>("open_app_dir").catch((err) =>
|
||||||
Notice.error(err?.message || err.toString(), 1500)
|
Notice.error(err?.message || err.toString(), 1500)
|
||||||
|
Loading…
Reference in New Issue
Block a user