Compare commits

...

10 Commits

31 changed files with 186 additions and 95 deletions

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@ dist-ssr
update.json
scripts/_env.sh
.vscode
.tool-version

1
.tool-versions Normal file
View File

@@ -0,0 +1 @@
nodejs 21.7.1

View File

@@ -9,6 +9,10 @@
A Clash Meta GUI based on <a href="https://github.com/tauri-apps/tauri">Tauri</a>.
</h3>
## Preview
![preview](./docs/preview.png)
## Install
请到发布页面下载对应的安装包:[Release page](https://github.com/clash-verge-rev/clash-verge-rev/releases)<br>
@@ -43,10 +47,6 @@ Supports Windows (x64/x86), Linux (x64/arm64) and macOS 10.15+ (intel/apple).
- Built-in support [Clash.Meta(mihomo)](https://github.com/MetaCubeX/mihomo) core.
- System proxy setting and guard.
## Preview
![preview](./docs/preview.gif)
### FAQ
Refer to [Doc FAQ Page](https://clash-verge-rev.github.io/faq.html)

View File

@@ -1,3 +1,17 @@
## v1.5.8
### Features
- 优化 UI 细节
- Linux 绘制窗口圆角
- 开放 DevTools
### Bugs Fixes
- 修复 MacOS 下开启 Tun 内核崩溃的问题
---
## v1.5.7
### Features

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 MiB

BIN
docs/preview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 KiB

View File

@@ -1,6 +1,6 @@
{
"name": "clash-verge",
"version": "1.5.7",
"version": "1.5.8",
"license": "GPL-3.0-only",
"scripts": {
"dev": "tauri dev",

View File

@@ -370,6 +370,16 @@ const resolveUninstall = () =>
file: "uninstall-service.exe",
downloadURL: `${SERVICE_URL}/uninstall-service.exe`,
});
const resolveSetDnsScript = () =>
resolveResource({
file: "set_dns.sh",
downloadURL: `https://github.com/clash-verge-rev/set-dns-script/releases/download/script/set_dns.sh`,
});
const resolveUnSetDnsScript = () =>
resolveResource({
file: "unset_dns.sh",
downloadURL: `https://github.com/clash-verge-rev/set-dns-script/releases/download/script/unset_dns.sh`,
});
const resolveMmdb = () =>
resolveResource({
file: "Country.mmdb",
@@ -409,6 +419,8 @@ const tasks = [
{ name: "service", func: resolveService, retry: 5, winOnly: true },
{ name: "install", func: resolveInstall, retry: 5, winOnly: true },
{ name: "uninstall", func: resolveUninstall, retry: 5, winOnly: true },
{ name: "set_dns_script", func: resolveSetDnsScript, retry: 5 },
{ name: "unset_dns_script", func: resolveUnSetDnsScript, retry: 5 },
{ name: "mmdb", func: resolveMmdb, retry: 5 },
{ name: "geosite", func: resolveGeosite, retry: 5 },
{ name: "geoip", func: resolveGeoIP, retry: 5 },

2
src-tauri/Cargo.lock generated
View File

@@ -598,7 +598,7 @@ dependencies = [
[[package]]
name = "clash-verge"
version = "1.5.7"
version = "1.5.8"
dependencies = [
"anyhow",
"auto-launch",

View File

@@ -1,6 +1,6 @@
[package]
name = "clash-verge"
version = "1.5.7"
version = "1.5.8"
description = "clash verge"
authors = ["zzzgydi", "wonfen", "MystiPanda"]
license = "GPL-3.0-only"
@@ -39,7 +39,7 @@ serde = { version = "1.0", features = ["derive"] }
reqwest = { version = "0.11", features = ["json", "rustls-tls"] }
sysproxy = { git="https://github.com/zzzgydi/sysproxy-rs", branch = "main" }
auto-launch = { git="https://github.com/zzzgydi/auto-launch", branch = "main" }
tauri = { version = "1.5", features = [ "path-all", "protocol-asset", "dialog-open", "notification-all", "icon-png", "clipboard-all", "global-shortcut-all", "process-all", "shell-all", "system-tray", "updater", "window-all"] }
tauri = { version = "1.5", features = [ "path-all", "protocol-asset", "dialog-open", "notification-all", "icon-png", "clipboard-all", "global-shortcut-all", "process-all", "shell-all", "system-tray", "updater", "window-all", "devtools"] }
[target.'cfg(windows)'.dependencies]
runas = "=1.0.0" # 高版本会返回错误 Status

View File

@@ -9,7 +9,7 @@ use anyhow::{Context, Result};
use serde_yaml::Mapping;
use std::collections::{HashMap, VecDeque};
use sysproxy::Sysproxy;
use tauri::api;
use tauri::{api, Manager};
type CmdResult<T = ()> = Result<T, String>;
#[tauri::command]
@@ -294,6 +294,17 @@ pub fn copy_icon_file(path: String, name: String) -> CmdResult<String> {
}
}
#[tauri::command]
pub fn open_devtools(app_handle: tauri::AppHandle) {
if let Some(window) = app_handle.get_window("main") {
if !window.is_devtools_open() {
window.open_devtools();
} else {
window.close_devtools();
}
}
}
#[tauri::command]
pub fn exit_app(app_handle: tauri::AppHandle) {
let _ = resolve::save_window_size_position(&app_handle, true);

View File

@@ -105,22 +105,6 @@ impl CoreManager {
sleep(Duration::from_millis(500)).await;
}
#[cfg(target_os = "macos")]
{
let enable_tun = Config::verge().latest().enable_tun_mode.clone();
let enable_tun = enable_tun.unwrap_or(false);
log::debug!(target: "app", "try to set system dns");
if enable_tun {
let script = include_str!("./script/set_dns.sh");
match (|| async { Command::new("bash").args([script]).output() })().await {
Ok(_) => return Ok(()),
Err(err) => {
log::error!(target: "app", "{err}");
}
}
}
}
#[cfg(target_os = "windows")]
{
use super::win_service;
@@ -263,22 +247,6 @@ impl CoreManager {
return Ok(());
}
#[cfg(target_os = "macos")]
{
let enable_tun = Config::verge().latest().enable_tun_mode.clone();
let enable_tun = enable_tun.unwrap_or(false);
log::debug!(target: "app", "try to unset system dns");
if enable_tun {
let script = include_str!("./script/unset_dns.sh");
match (|| Command::new("bash").args([script]).output())() {
Ok(_) => return Ok(()),
Err(err) => {
log::error!(target: "app", "{err}");
}
}
}
}
let mut sidecar = self.sidecar.lock();
if let Some(child) = sidecar.take() {
log::debug!(target: "app", "stop the core by sidecar");

View File

@@ -1,5 +0,0 @@
nic=$(route -n get default | grep "interface" | awk '{print $2}')
hardware_port=$(networksetup -listallhardwareports | awk -v dev="$nic" '/Hardware Port/{port=$3} /Device:/{if ($2 == dev) {print port; exit}}')
networksetup -setdnsservers $hardware_port 223.5.5.5

View File

@@ -1,5 +0,0 @@
nic=$(route -n get default | grep "interface" | awk '{print $2}')
hardware_port=$(networksetup -listallhardwareports | awk -v dev="$nic" '/Hardware Port/{port=$3} /Device:/{if ($2 == dev) {print port; exit}}')
networksetup -setdnsservers $hardware_port Empty

View File

@@ -34,8 +34,38 @@ pub fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
revise!(config, "tun", tun_val);
if enable {
#[cfg(target_os = "macos")]
{
use crate::utils::dirs;
use tauri::api::process::Command;
log::info!(target: "app", "try to set system dns");
let resource_dir = dirs::app_resources_dir().unwrap();
let script = resource_dir.join("set_dns.sh");
let script = script.to_string_lossy();
match Command::new("bash").args([script]).output() {
Ok(_) => log::info!(target: "app", "set system dns successfully"),
Err(err) => {
log::error!(target: "app", "set system dns failed: {err}");
}
}
}
use_dns_for_tun(config)
} else {
#[cfg(target_os = "macos")]
{
use crate::utils::dirs;
use tauri::api::process::Command;
log::info!(target: "app", "try to unset system dns");
let resource_dir = dirs::app_resources_dir().unwrap();
let script = resource_dir.join("unset_dns.sh");
let script = script.to_string_lossy();
match Command::new("bash").args([script]).output() {
Ok(_) => log::info!(target: "app", "unset system dns successfully"),
Err(err) => {
log::error!(target: "app", "unset system dns failed: {err}");
}
}
}
config
}
}

View File

@@ -57,6 +57,7 @@ fn main() -> std::io::Result<()> {
cmds::test_delay,
cmds::get_app_dir,
cmds::copy_icon_file,
cmds::open_devtools,
cmds::exit_app,
// cmds::update_hotkeys,
// profile

View File

@@ -171,7 +171,7 @@ pub fn create_window(app_handle: &AppHandle) {
.title_bar_style(tauri::TitleBarStyle::Overlay)
.build();
#[cfg(target_os = "linux")]
let window = builder.decorations(true).transparent(false).build();
let window = builder.decorations(false).transparent(true).build();
match window {
Ok(win) => {

View File

@@ -1,7 +1,7 @@
{
"package": {
"productName": "Clash Verge",
"version": "1.5.7"
"version": "1.5.8"
},
"build": {
"distDir": "../dist",

View File

@@ -47,11 +47,11 @@ body {
@import "./page.scss";
@import "./font.scss";
@media (prefers-color-scheme: dark) {
:root {
background-color: rgba(18, 18, 18, 1);
}
}
// @media (prefers-color-scheme: dark) {
// :root {
// background-color: rgba(18, 18, 18, 1);
// }
// }
.user-none {
user-select: none;

View File

@@ -37,7 +37,7 @@
justify-content: center;
align-items: flex-start;
align-self: stretch;
border-bottom: 1px solid var(--divider-color);
// border-bottom: 1px solid var(--divider-color);
// max-width: $maxLogo + 32px;
// max-height: $maxLogo;
// margin: 0 auto;
@@ -94,7 +94,7 @@
// position: absolute;
// top: 0px;
// right: 0px;
height: 24px;
height: 36px;
display: flex;
// align-items: center;
justify-content: end;

View File

@@ -243,6 +243,7 @@ export const ProfileItem = (props: Props) => {
<Typography
width="calc(100% - 36px)"
sx={{ fontSize: "18px", fontWeight: "600", lineHeight: "26px" }}
variant="h6"
component="h2"
noWrap
@@ -279,7 +280,11 @@ export const ProfileItem = (props: Props) => {
{
<>
{description ? (
<Typography noWrap title={description}>
<Typography
noWrap
title={description}
sx={{ fontSize: "14px" }}
>
{description}
</Typography>
) : (
@@ -312,7 +317,7 @@ export const ProfileItem = (props: Props) => {
<span title="Expire Time">{expire}</span>
</Box>
) : (
<Box sx={{ ...boxStyle, fontSize: 14, justifyContent: "flex-end" }}>
<Box sx={{ ...boxStyle, fontSize: 12, justifyContent: "flex-end" }}>
<span title="Updated Time">{parseExpire(updated)}</span>
</Box>
)}

View File

@@ -10,7 +10,13 @@ import {
Input,
Typography,
} from "@mui/material";
import { exitApp, openAppDir, openCoreDir, openLogsDir } from "@/services/cmds";
import {
exitApp,
openAppDir,
openCoreDir,
openLogsDir,
openDevTools,
} from "@/services/cmds";
import { ArrowForward } from "@mui/icons-material";
import { checkUpdate } from "@tauri-apps/api/updater";
import { useVerge } from "@/hooks/use-verge";
@@ -27,6 +33,7 @@ import { LayoutViewer } from "./mods/layout-viewer";
import { UpdateViewer } from "./mods/update-viewer";
import getSystem from "@/utils/get-system";
import { routers } from "@/pages/_routers";
import { appWindow } from "@tauri-apps/api/window";
interface Props {
onError?: (err: Error) => void;
}
@@ -304,6 +311,17 @@ const SettingVerge = ({ onError }: Props) => {
</IconButton>
</SettingItem>
<SettingItem label={t("Open Dev Tools")}>
<IconButton
color="inherit"
size="small"
sx={{ my: "2px" }}
onClick={openDevTools}
>
<ArrowForward />
</IconButton>
</SettingItem>
<SettingItem label={t("Exit")}>
<IconButton
color="inherit"

View File

@@ -123,6 +123,7 @@
"Open Core Dir": "内核目录",
"Open Logs Dir": "日志目录",
"Check for Updates": "检查更新",
"Open Dev Tools": "打开开发者工具",
"Verge Version": "Verge 版本",
"theme.light": "浅色",
"theme.dark": "深色",

View File

@@ -122,6 +122,14 @@ const Layout = () => {
({ palette }) => ({
bgcolor: palette.background.paper,
}),
OS === "linux"
? {
borderRadius: "8px",
border: "2px solid var(--divider-color)",
width: "calc(100vw - 4px)",
height: "calc(100vh - 4px)",
}
: {},
]}
>
<div className="layout__left" data-tauri-drag-region="true">
@@ -148,11 +156,11 @@ const Layout = () => {
</div>
<div className="layout__right">
{OS === "windows" && (
{
<div className="the-bar" data-tauri-drag-region="true">
<LayoutControl />
{OS !== "macos" && <LayoutControl />}
</div>
)}
}
<TransitionGroup className="the-content">
<CSSTransition

View File

@@ -4,7 +4,7 @@ const OS = getSystem();
// default theme setting
export const defaultTheme = {
primary_color: "#007AFF",
secondary_color: "#FFCC00",
secondary_color: "#fc9b76",
primary_text: "#000000",
secondary_text: "#3c3c4399",
info_color: "#007AFF",

View File

@@ -200,10 +200,10 @@ const ConnectionsPage = () => {
</Box>
<Box
height="calc(100% - 70px)"
height="calc(100% - 65px)"
sx={{
userSelect: "text",
margin: "12px",
margin: "10px",
borderRadius: "8px",
bgcolor: isDark ? "#282a36" : "#ffffff",
}}

View File

@@ -110,9 +110,9 @@ const LogPage = () => {
</Box>
<Box
height="calc(100% - 70px)"
height="calc(100% - 65px)"
sx={{
margin: "12px",
margin: "10px",
borderRadius: "8px",
bgcolor: isDark ? "#282a36" : "#ffffff",
}}

View File

@@ -2,7 +2,15 @@ import useSWR, { mutate } from "swr";
import { useMemo, useRef, useState } from "react";
import { useLockFn } from "ahooks";
import { useSetRecoilState } from "recoil";
import { Box, Button, Grid, IconButton, Stack, TextField } from "@mui/material";
import {
Box,
Button,
Grid,
IconButton,
Stack,
TextField,
Divider,
} from "@mui/material";
import {
DndContext,
closestCenter,
@@ -46,6 +54,8 @@ import { ProfileMore } from "@/components/profile/profile-more";
import { useProfiles } from "@/hooks/use-profiles";
import { ConfigViewer } from "@/components/setting/mods/config-viewer";
import { throttle } from "lodash-es";
import { useRecoilState } from "recoil";
import { atomThemeMode } from "@/services/states";
const ProfilePage = () => {
const { t } = useTranslation();
@@ -235,6 +245,11 @@ const ProfilePage = () => {
const text = await navigator.clipboard.readText();
if (text) setUrl(text);
};
const [mode] = useRecoilState(atomThemeMode);
const islight = mode === "light" ? true : false;
const dividercolor = islight
? "rgba(0, 0, 0, 0.06)"
: "rgba(255, 255, 255, 0.06)";
return (
<BasePage
@@ -323,6 +338,7 @@ const ProfilePage = () => {
loading={loading}
variant="contained"
size="small"
sx={{ borderRadius: "6px" }}
onClick={onImport}
>
{t("Import")}
@@ -330,6 +346,7 @@ const ProfilePage = () => {
<Button
variant="contained"
size="small"
sx={{ borderRadius: "6px" }}
onClick={() => viewerRef.current?.create()}
>
{t("New")}
@@ -341,7 +358,7 @@ const ProfilePage = () => {
mb: 0.5,
pl: "10px",
mr: "10px",
height: "calc(100% - 20px)",
height: "calc(100% - 68px)",
overflowY: "auto",
}}
>
@@ -350,7 +367,7 @@ const ProfilePage = () => {
collisionDetection={closestCenter}
onDragEnd={onDragEnd}
>
<Box sx={{ mb: 4.5 }}>
<Box sx={{ mb: 1.5 }}>
<Grid container spacing={{ xs: 1, lg: 1 }}>
<SortableContext
items={regularItems.map((x) => {
@@ -375,24 +392,34 @@ const ProfilePage = () => {
</DndContext>
{enhanceItems.length > 0 && (
<Grid container spacing={{ xs: 2, lg: 2 }}>
{enhanceItems.map((item) => (
<Grid item xs={12} sm={6} md={4} lg={3} key={item.file}>
<ProfileMore
selected={!!chain.includes(item.uid)}
itemData={item}
enableNum={chain.length || 0}
logInfo={chainLogs[item.uid]}
onEnable={() => onEnable(item.uid)}
onDisable={() => onDisable(item.uid)}
onDelete={() => onDelete(item.uid)}
onMoveTop={() => onMoveTop(item.uid)}
onMoveEnd={() => onMoveEnd(item.uid)}
onEdit={() => viewerRef.current?.edit(item)}
/>
</Grid>
))}
</Grid>
<Divider
variant="middle"
flexItem
sx={{ width: `calc(100% - 32px)`, borderColor: dividercolor }}
></Divider>
)}
{enhanceItems.length > 0 && (
<Box sx={{ mt: 1.5 }}>
<Grid container spacing={{ xs: 1, lg: 1 }}>
{enhanceItems.map((item) => (
<Grid item xs={12} sm={6} md={4} lg={3} key={item.file}>
<ProfileMore
selected={!!chain.includes(item.uid)}
itemData={item}
enableNum={chain.length || 0}
logInfo={chainLogs[item.uid]}
onEnable={() => onEnable(item.uid)}
onDisable={() => onDisable(item.uid)}
onDelete={() => onDelete(item.uid)}
onMoveTop={() => onMoveTop(item.uid)}
onMoveEnd={() => onMoveEnd(item.uid)}
onEdit={() => viewerRef.current?.edit(item)}
/>
</Grid>
))}
</Grid>
</Box>
)}
</Box>
<ProfileViewer ref={viewerRef} onChange={() => mutateProfiles()} />

View File

@@ -56,9 +56,9 @@ const RulesPage = () => {
</Box>
<Box
height="calc(100% - 70px)"
height="calc(100% - 65px)"
sx={{
margin: "12px",
margin: "10px",
borderRadius: "8px",
bgcolor: isDark ? "#282a36" : "#ffffff",
}}

View File

@@ -134,7 +134,7 @@ const TestPage = () => {
>
<Box
sx={{
pt: 1,
pt: 1.25,
mb: 0.5,
px: "10px",
}}

View File

@@ -213,6 +213,10 @@ export async function getPortableFlag() {
return invoke<boolean>("get_portable_flag");
}
export async function openDevTools() {
return invoke("open_devtools");
}
export async function exitApp() {
return invoke("exit_app");
}