diff --git a/package.json b/package.json index 88daf360ddaeafea6977dbd82dd6abc75762ce0a..e670c94710d92cf25d825b423fa59cc2ea2db50c 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "scripts": { "dev": "tauri dev", "dev:diff": "tauri dev -f verge-dev -c src-tauri/tauri.diff.json", - "dev:meta": "tauri dev -f meta-dev -c src-tauri/tauri.meta.json", + "dev:core": "cross-env VITE_MULTI_CORE=1 yarn dev:diff", "build": "tauri build", "tauri": "tauri", "web:dev": "vite", @@ -47,6 +47,7 @@ "@types/react-dom": "^17.0.0", "@vitejs/plugin-react": "^1.3.2", "adm-zip": "^0.5.9", + "cross-env": "^7.0.3", "fs-extra": "^10.0.0", "https-proxy-agent": "^5.0.1", "husky": "^7.0.0", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 9b4c8c88378e727733c98ee409f5bde13613e315..07985081176104911e93b98b9b8fb20a553a2f1b 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -45,9 +45,7 @@ windows-sys = { version = "0.36", features = ["Win32_System_LibraryLoader", "Win [features] default = ["custom-protocol", "tauri/ayatana-tray"] custom-protocol = ["tauri/custom-protocol"] -meta-dev = ["clash-meta", "verge-dev"] verge-dev = [] -clash-meta = [] debug-yml = [] [profile.release] diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs index bb65a196ef19637598d6815ef364b89ff4c04198..f3aa877bf0c2b2026f4a33c19e9e9c522a62bb30 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -196,10 +196,16 @@ pub fn patch_verge_config( payload: Verge, app_handle: tauri::AppHandle, core: State<'_, Core>, -) -> Result<(), String> { +) -> CmdResult { wrap_err!(core.patch_verge(payload, &app_handle)) } +/// change clash core +#[tauri::command] +pub fn change_clash_core(core: State<'_, Core>, clash_core: Option<String>) -> CmdResult { + wrap_err!(core.change_core(clash_core)) +} + /// restart the sidecar #[tauri::command] pub fn restart_sidecar(core: State<'_, Core>) -> CmdResult { diff --git a/src-tauri/src/core/mod.rs b/src-tauri/src/core/mod.rs index e7be3bb109931844ba930db59438b87c843f14fb..5907cf4cbaa3c64ae03cd3afc5ea818fa9c6417b 100644 --- a/src-tauri/src/core/mod.rs +++ b/src-tauri/src/core/mod.rs @@ -65,22 +65,21 @@ impl Core { /// initialize the core state pub fn init(&self, app_handle: tauri::AppHandle) { + let verge = self.verge.lock(); + let clash_core = verge.clash_core.clone(); + + let mut service = self.service.lock(); + service.set_core(clash_core); + #[cfg(windows)] { - let verge = self.verge.lock(); let enable = verge.enable_service_mode.clone(); - - let mut service = self.service.lock(); service.set_mode(enable.unwrap_or(false)); - - log_if_err!(service.start()); } - #[cfg(not(windows))] - { - let mut service = self.service.lock(); - log_if_err!(service.start()); - } + log_if_err!(service.start()); + drop(verge); + drop(service); log_if_err!(self.activate()); @@ -138,6 +137,31 @@ impl Core { self.activate_enhanced(true) } + /// change the clash core + pub fn change_core(&self, clash_core: Option<String>) -> Result<()> { + let clash_core = clash_core.unwrap_or("clash".into()); + + if &clash_core != "clash" && &clash_core != "clash-meta" { + bail!("invalid clash core name \"{clash_core}\""); + } + + let mut verge = self.verge.lock(); + verge.patch_config(Verge { + clash_core: Some(clash_core.clone()), + ..Verge::default() + })?; + drop(verge); + + let mut service = self.service.lock(); + service.stop()?; + service.set_core(Some(clash_core)); + service.start()?; + drop(service); + + self.activate()?; + self.activate_enhanced(true) + } + /// Patch Clash /// handle the clash config changed pub fn patch_clash(&self, patch: Mapping) -> Result<()> { diff --git a/src-tauri/src/core/multi.rs b/src-tauri/src/core/multi.rs new file mode 100644 index 0000000000000000000000000000000000000000..d77983062af0c92924e40db791b2c42fa3d44ff3 --- /dev/null +++ b/src-tauri/src/core/multi.rs @@ -0,0 +1,26 @@ +use anyhow::{Context, Result}; +use std::env::current_exe; + +pub struct CoreItem { + pub name: String, + pub path: String, +} + +pub struct Multi {} + +impl Multi { + pub fn list() -> Result<Vec<CoreItem>> { + let paths = current_exe() + .unwrap() + .parent() + .unwrap() + .read_dir() + .context("failed to current dir")?; + + for path in paths { + dbg!(path.unwrap().path().metadata().unwrap().permissions().); + } + + Ok(vec![]) + } +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 8abe5e7c7bfe442d009d524d7d67c72bcb029b5a..0cec26d145f1fe2adc693c95cfce2c287fae9e2e 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -100,15 +100,16 @@ fn main() -> std::io::Result<()> { }) .invoke_handler(tauri::generate_handler![ // common - cmds::restart_sidecar, cmds::get_sys_proxy, cmds::get_cur_proxy, - cmds::kill_sidecar, cmds::open_app_dir, cmds::open_logs_dir, + cmds::kill_sidecar, + cmds::restart_sidecar, // clash cmds::get_clash_info, cmds::patch_clash_config, + cmds::change_clash_core, // verge cmds::get_verge_config, cmds::patch_verge_config, diff --git a/src-tauri/tauri.diff.json b/src-tauri/tauri.diff.json index afae5ff0238162105176a6790e9db1073d817445..cd2618c1161f0e6566028bff23c09e17a06ed5fe 100644 --- a/src-tauri/tauri.diff.json +++ b/src-tauri/tauri.diff.json @@ -1,9 +1,8 @@ -{ - "tauri": { - "bundle": { - "identifier": "top.gydi.clashverge.dev", - "externalBin": ["sidecar/clash", "sidecar/clash-meta"] - } - } - } - \ No newline at end of file +{ + "tauri": { + "bundle": { + "identifier": "top.gydi.clashverge.dev", + "externalBin": ["sidecar/clash", "sidecar/clash-meta"] + } + } +} diff --git a/src-tauri/tauri.meta.json b/src-tauri/tauri.meta.json deleted file mode 100644 index 9f4bb77979796ce1759622a37a0071fcf61c1490..0000000000000000000000000000000000000000 --- a/src-tauri/tauri.meta.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "tauri": { - "bundle": { - "externalBin": ["sidecar/clash", "sidecar/clash-meta"] - } - } -} diff --git a/src/components/setting/core-switch.tsx b/src/components/setting/core-switch.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b7ebd08295a85b5a6ae3ce3b9ab0f6dac476e117 --- /dev/null +++ b/src/components/setting/core-switch.tsx @@ -0,0 +1,84 @@ +import useSWR, { useSWRConfig } from "swr"; +import { useState } from "react"; +import { useLockFn } from "ahooks"; +import { Menu, MenuItem } from "@mui/material"; +import { Settings } from "@mui/icons-material"; +import { changeClashCore, getVergeConfig } from "../../services/cmds"; +import getSystem from "../../utils/get-system"; +import Notice from "../base/base-notice"; + +const OS = getSystem(); + +const VALID_CORE = [ + { name: "Clash", core: "clash" }, + { name: "Clash Meta", core: "clash-meta" }, +]; + +const CoreSwitch = () => { + const { mutate } = useSWRConfig(); + + const { data: vergeConfig } = useSWR("getVergeConfig", getVergeConfig); + + const [anchorEl, setAnchorEl] = useState<any>(null); + const [position, setPosition] = useState({ left: 0, top: 0 }); + + const { clash_core = "clash" } = vergeConfig ?? {}; + + const onCoreChange = useLockFn(async (core: string) => { + if (core === clash_core) return; + + try { + await changeClashCore(core); + mutate("getVergeConfig"); + mutate("getClashConfig"); + mutate("getVersion"); + setAnchorEl(null); + Notice.success(`Successfully switch to ${core}`, 1000); + } catch (err: any) { + Notice.error(err?.message || err.toString()); + } + }); + + return ( + <> + <Settings + fontSize="small" + style={{ cursor: "pointer" }} + onClick={(event) => { + const { clientX, clientY } = event; + setPosition({ top: clientY, left: clientX }); + setAnchorEl(event.currentTarget); + }} + /> + + <Menu + open={!!anchorEl} + anchorEl={anchorEl} + onClose={() => setAnchorEl(null)} + anchorPosition={position} + anchorReference="anchorPosition" + transitionDuration={225} + TransitionProps={ + OS === "macos" ? { style: { transitionDuration: "225ms" } } : {} + } + onContextMenu={(e) => { + setAnchorEl(null); + e.preventDefault(); + }} + > + {VALID_CORE.map((each) => ( + <MenuItem + key={each.core} + sx={{ minWidth: 125 }} + selected={each.core === clash_core} + onClick={() => onCoreChange(each.core)} + > + {each.name} + </MenuItem> + ))} + </Menu> + </> + ); +}; + +export default CoreSwitch; diff --git a/src/components/setting/setting-clash.tsx b/src/components/setting/setting-clash.tsx index 6cd10a652c48e32179d37100766b8ed3cf189fed..536cf7c0ecc51f6d152af97cfc34c99200552861 100644 --- a/src/components/setting/setting-clash.tsx +++ b/src/components/setting/setting-clash.tsx @@ -8,6 +8,7 @@ import { Select, MenuItem, Typography, + Box, } from "@mui/material"; import { ApiType } from "../../services/types"; import { atomClashPort } from "../../services/states"; @@ -16,11 +17,14 @@ import { SettingList, SettingItem } from "./setting"; import { getClashConfig, getVersion, updateConfigs } from "../../services/api"; import Notice from "../base/base-notice"; import GuardState from "./guard-state"; +import CoreSwitch from "./core-switch"; interface Props { onError: (err: Error) => void; } +const MULTI_CORE = !!import.meta.env.VITE_MULTI_CORE; + const SettingClash = ({ onError }: Props) => { const { t } = useTranslation(); const { mutate } = useSWRConfig(); @@ -54,7 +58,7 @@ const SettingClash = ({ onError }: Props) => { } await patchClashConfig({ "mixed-port": port }); setGlobalClashPort(port); - Notice.success("Change Clash port successfully!"); + Notice.success("Change Clash port successfully!", 1000); // update the config mutate("getClashConfig"); @@ -129,7 +133,18 @@ const SettingClash = ({ onError }: Props) => { </SettingItem> <SettingItem> - <ListItemText primary={t("Clash Core")} /> + <ListItemText + primary={ + MULTI_CORE ? ( + <Box sx={{ display: "flex", alignItems: "center" }}> + <span style={{ marginRight: 4 }}>{t("Clash Core")}</span> + <CoreSwitch /> + </Box> + ) : ( + t("Clash Core") + ) + } + /> <Typography sx={{ py: 1 }}>{clashVer}</Typography> </SettingItem> </SettingList> diff --git a/src/services/cmds.ts b/src/services/cmds.ts index 6ef06457a462ae95fe36948d9e17715b164e21b6..fd7f99ad6ed2b6da63d773175cedc3cc4f8983ef 100644 --- a/src/services/cmds.ts +++ b/src/services/cmds.ts @@ -86,6 +86,10 @@ export async function getSystemProxy() { return invoke<any>("get_sys_proxy"); } +export async function changeClashCore(clashCore: string) { + return invoke<any>("change_clash_core", { clashCore }); +} + export async function restartSidecar() { return invoke<void>("restart_sidecar"); } diff --git a/src/services/types.ts b/src/services/types.ts index 9a38b2dee2347e2711eeb577f10d05544da6968c..44b8e7b4619e35f58eccd673e5aaebb72df5f204 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -1,7 +1,7 @@ /** * Some interface for clash api */ - export namespace ApiType { +export namespace ApiType { export interface ConfigData { port: number; mode: string; @@ -125,6 +125,7 @@ export namespace CmdType { export interface VergeConfig { language?: string; + clash_core?: string; theme_mode?: "light" | "dark"; theme_blur?: boolean; traffic_graph?: boolean; diff --git a/yarn.lock b/yarn.lock index ad94a2d5122081ab6813dfe0506d7162ae770626..25f7ddeb2bf9f3292d289a6aee78d1f4a84f03e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1201,7 +1201,14 @@ cosmiconfig@^7.0.1: path-type "^4.0.0" yaml "^1.10.0" -cross-spawn@^7.0.0: +cross-env@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" + integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== + dependencies: + cross-spawn "^7.0.1" + +cross-spawn@^7.0.0, cross-spawn@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==