Compare commits

..

20 Commits

21 changed files with 128 additions and 108 deletions

View File

@@ -1,3 +1,17 @@
## v1.1.1
### Features
- optimize clash config feedback
- hide macOS dock icon
- use clash meta compatible version (Linux)
### Bug Fixes
- fix some other glitches
---
## v1.1.0
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "clash-verge",
"version": "1.1.0",
"version": "1.1.1",
"license": "GPL-3.0",
"scripts": {
"dev": "tauri dev",

View File

@@ -28,6 +28,7 @@ function resolveClash() {
"darwin-x64": "clash-darwin-amd64",
"darwin-arm64": "clash-darwin-arm64",
"linux-x64": "clash-linux-amd64",
"linux-arm64": "clash-linux-armv8",
};
const name = map[`${platform}-${arch}`];
@@ -58,7 +59,8 @@ async function resolveClashMeta() {
"win32-x64": "Clash.Meta-windows-amd64",
"darwin-x64": "Clash.Meta-darwin-amd64",
"darwin-arm64": "Clash.Meta-darwin-arm64",
"linux-x64": "Clash.Meta-linux-amd64",
"linux-x64": "Clash.Meta-linux-amd64-compatible",
"linux-arm64": "Clash.Meta-linux-arm64",
};
const name = map[`${platform}-${arch}`];

View File

@@ -40,6 +40,12 @@ impl Handle {
}
}
pub fn notice_message(&self, status: String, msg: String) {
if let Some(window) = self.get_window() {
log_if_err!(window.emit("verge://notice-message", (status, msg)));
}
}
// update system tray state (clash config)
pub fn update_systray_clash(&self) -> Result<()> {
if self.app_handle.is_none() {

View File

@@ -284,9 +284,14 @@ impl Core {
match Service::set_config(clash_info, config).await {
Ok(_) => {
let handle = handle.lock();
handle.refresh_clash()
handle.refresh_clash();
handle.notice_message("set_config::ok".into(), "ok".into());
}
Err(err) => {
let handle = handle.lock();
handle.notice_message("set_config::error".into(), format!("{err}"));
log::error!(target: "app", "last {err}")
}
Err(err) => log::error!(target: "app", "{err}"),
}
});

View File

@@ -129,8 +129,15 @@ impl Service {
let app_dir = dirs::app_home_dir();
let app_dir = app_dir.as_os_str().to_str().unwrap();
// fix #212
let args = match clash_core.as_str() {
"clash-meta" => vec!["-m", "-d", app_dir],
_ => vec!["-d", app_dir],
};
let cmd = Command::new_sidecar(clash_core)?;
let (mut rx, cmd_child) = cmd.args(["-d", app_dir]).spawn()?;
let (mut rx, cmd_child) = cmd.args(args).spawn()?;
// 将pid写入文件中
let pid = cmd_child.pid();
@@ -212,8 +219,17 @@ impl Service {
let mut data = HashMap::new();
data.insert("path", temp_path.as_os_str().to_str().unwrap());
macro_rules! report_err {
($i: expr, $e: expr) => {
match $i {
4 => bail!($e),
_ => log::error!(target: "app", $e),
}
};
}
// retry 5 times
for _ in 0..5 {
for i in 0..5 {
let headers = headers.clone();
match reqwest::ClientBuilder::new().no_proxy().build() {
Ok(client) => {
@@ -223,14 +239,12 @@ impl Service {
204 => break,
// 配置有问题不重试
400 => bail!("failed to update clash config with status 400"),
status @ _ => {
log::error!(target: "app", "failed to activate clash with status \"{status}\"");
}
status @ _ => report_err!(i, "failed to activate clash with status \"{status}\""),
},
Err(err) => log::error!(target: "app", "{err}"),
Err(err) => report_err!(i, "{err}"),
}
}
Err(err) => log::error!(target: "app", "{err}"),
Err(err) => report_err!(i, "{err}"),
}
sleep(Duration::from_millis(500)).await;
}

View File

@@ -170,6 +170,12 @@ impl Sysopt {
self.auto_launch = Some(auto);
// 避免在开发时将自启动关了
#[cfg(feature = "verge-dev")]
if !enable {
return Ok(());
}
let auto = self.auto_launch.as_ref().unwrap();
// macos每次启动都更新登录项避免重复设置登录项

View File

@@ -19,18 +19,12 @@ use tauri::{
};
fn main() -> std::io::Result<()> {
let mut context = tauri::generate_context!();
{
let verge = Verge::new();
let verge = Verge::new();
if server::check_singleton(verge.app_singleton_port).is_err() {
println!("app exists");
return Ok(());
}
for win in context.config_mut().tauri.windows.iter_mut() {
if verge.enable_silent_start.unwrap_or(false) {
win.visible = false;
if server::check_singleton(verge.app_singleton_port).is_err() {
println!("app exists");
return Ok(());
}
}
@@ -145,10 +139,14 @@ fn main() -> std::io::Result<()> {
builder = builder.menu(Menu::new().add_submenu(submenu_file));
}
let app = builder
.build(context)
#[allow(unused_mut)]
let mut app = builder
.build(tauri::generate_context!())
.expect("error while running tauri application");
#[cfg(target_os = "macos")]
app.set_activation_policy(tauri::ActivationPolicy::Accessory);
let app_handle = app.app_handle();
ctrlc::set_handler(move || {
resolve::resolve_reset();

View File

@@ -6,20 +6,24 @@ pub fn resolve_setup(app: &App) {
// init app config
init::init_app(app.package_info());
{
let silent_start = {
let global = Data::global();
let verge = global.verge.lock();
let singleton = verge.app_singleton_port.clone();
// setup a simple http server for singleton
server::embed_server(&app.handle(), singleton);
}
verge.enable_silent_start.clone().unwrap_or(false)
};
// core should be initialized after init_app fix #122
let core = Core::global();
core.init(app.app_handle());
resolve_window(app);
if !silent_start {
create_window(&app.app_handle());
}
}
/// reset system proxy
@@ -33,39 +37,6 @@ pub fn resolve_reset() {
crate::log_if_err!(service.stop());
}
/// customize the window theme
fn resolve_window(app: &App) {
let window = app.get_window("main").unwrap();
#[cfg(target_os = "windows")]
{
use crate::utils::winhelp;
use window_shadows::set_shadow;
use window_vibrancy::apply_blur;
let _ = window.set_decorations(false);
let _ = set_shadow(&window, true);
// todo
// win11 disable this feature temporarily due to lag
if !winhelp::is_win11() {
let _ = apply_blur(&window, None);
}
}
#[cfg(target_os = "macos")]
{
use tauri::LogicalSize;
use tauri::Size::Logical;
let _ = window.set_decorations(true);
let _ = window.set_size(Logical(LogicalSize {
width: 800.0,
height: 620.0,
}));
}
}
/// create main window
pub fn create_window(app_handle: &AppHandle) {
if let Some(window) = app_handle.get_window("main") {
@@ -120,7 +91,7 @@ pub fn create_window(app_handle: &AppHandle) {
}
#[cfg(target_os = "macos")]
crate::log_if_err!(builder.decorations(true).inner_size(800.0, 620.0).build());
crate::log_if_err!(builder.decorations(true).inner_size(800.0, 642.0).build());
#[cfg(target_os = "linux")]
crate::log_if_err!(builder

View File

@@ -1,7 +1,7 @@
{
"package": {
"productName": "Clash Verge",
"version": "1.1.0"
"version": "1.1.1"
},
"build": {
"distDir": "../dist",
@@ -81,20 +81,7 @@
"all": true
}
},
"windows": [
{
"title": "Clash Verge",
"width": 800,
"height": 636,
"center": true,
"resizable": true,
"fullscreen": false,
"decorations": false,
"transparent": true,
"minWidth": 600,
"minHeight": 520
}
],
"windows": [],
"security": {
"csp": "script-src 'unsafe-eval' 'self'; default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'; img-src data: 'self';"
}

View File

@@ -31,3 +31,9 @@ body {
@import "./layout.scss";
@import "./page.scss";
@media (prefers-color-scheme: dark) {
:root {
background-color: rgba(18, 18, 18, 1);
}
}

View File

@@ -67,7 +67,6 @@ export default function useCustomTheme() {
const scrollColor = mode === "light" ? "#90939980" : "#54545480";
const rootEle = document.documentElement;
rootEle.style.background = "transparent";
rootEle.style.setProperty("--selection-color", selectColor);
rootEle.style.setProperty("--scroller-color", scrollColor);
rootEle.style.setProperty("--primary-main", theme.palette.primary.main);

View File

@@ -33,7 +33,7 @@ const EnhancedMode = (props: Props) => {
try {
await enhanceProfiles();
mutateLogs();
Notice.success("Refresh clash config", 1000);
// Notice.success("Refresh clash config", 1000);
} catch (err: any) {
Notice.error(err.message || err.toString());
}

View File

@@ -23,8 +23,8 @@ export default function useSortProxy(
const ad = delayManager.getDelay(a.name, groupName);
const bd = delayManager.getDelay(b.name, groupName);
if (ad === -1) return 1;
if (bd === -1) return -1;
if (ad === -1 || ad === -2) return 1;
if (bd === -1 || bd === -2) return -1;
return ad - bd;
});

View File

@@ -93,7 +93,7 @@ const ClashFieldViewer = ({ handler }: Props) => {
try {
await changeProfileValid([...curSet]);
mutateProfile();
Notice.success("Refresh clash config", 1000);
// Notice.success("Refresh clash config", 1000);
} catch (err: any) {
Notice.error(err?.message || err.toString());
}

View File

@@ -14,6 +14,7 @@ import { getAxios } from "@/services/api";
import { atomCurrentProfile } from "@/services/states";
import { getVergeConfig, getProfiles } from "@/services/cmds";
import { ReactComponent as LogoSvg } from "@/assets/image/logo.svg";
import Notice from "@/components/base/base-notice";
import LayoutItem from "@/components/layout/layout-item";
import LayoutControl from "@/components/layout/layout-control";
import LayoutTraffic from "@/components/layout/layout-traffic";
@@ -59,6 +60,21 @@ const Layout = () => {
// update the verge config
listen("verge://refresh-verge-config", () => mutate("getVergeConfig"));
// 设置提示监听
listen("verge://notice-message", ({ payload }) => {
const [status, msg] = payload as [string, string];
switch (status) {
case "set_config::ok":
Notice.success("Refresh clash config");
break;
case "set_config::error":
Notice.error(msg);
break;
default:
break;
}
});
// set current profile uid
getProfiles().then((data) => setCurrentProfile(data.current ?? ""));
}, []);

View File

@@ -44,12 +44,12 @@ const ConnectionsPage = () => {
const filterConn = useMemo(() => {
const orderFunc = orderOpts[curOrderOpt];
const connetions = connData.connections.filter((conn) =>
const connections = connData.connections.filter((conn) =>
(conn.metadata.host || conn.metadata.destinationIP)?.includes(filterText)
);
if (orderFunc) return orderFunc(connetions);
return connetions;
if (orderFunc) return orderFunc(connections);
return connections;
}, [connData, filterText, curOrderOpt]);
useEffect(() => {
@@ -167,6 +167,7 @@ const ConnectionsPage = () => {
fullWidth
size="small"
autoComplete="off"
spellCheck="false"
variant="outlined"
placeholder={t("Filter conditions")}
value={filterText}

View File

@@ -93,6 +93,7 @@ const LogPage = () => {
fullWidth
size="small"
autoComplete="off"
spellCheck="false"
variant="outlined"
placeholder={t("Filter conditions")}
value={filterText}

View File

@@ -133,7 +133,7 @@ const ProfilePage = () => {
setCurrentProfile(uid);
mutate("getProfiles", { ...profiles, current: uid }, true);
mutate("getRuntimeLogs");
if (force) Notice.success("Refresh clash config", 1000);
// if (force) Notice.success("Refresh clash config", 1000);
} catch (err: any) {
Notice.error(err?.message || err.toString());
}
@@ -149,6 +149,7 @@ const ProfilePage = () => {
value={url}
variant="outlined"
autoComplete="off"
spellCheck="false"
onChange={(e) => setUrl(e.target.value)}
sx={{ input: { py: 0.65, px: 1.25 } }}
placeholder={t("Profile URL")}

View File

@@ -37,6 +37,7 @@ const RulesPage = () => {
size="small"
autoComplete="off"
variant="outlined"
spellCheck="false"
placeholder={t("Filter conditions")}
value={filterText}
onChange={(e) => setFilterText(e.target.value)}

View File

@@ -88,13 +88,9 @@ export async function updateProxy(group: string, proxy: string) {
// get proxy
async function getProxiesInner() {
try {
const instance = await getAxios();
const response = await instance.get<any, any>("/proxies");
return (response?.proxies || {}) as Record<string, ApiType.ProxyItem>;
} catch {
return {};
}
const instance = await getAxios();
const response = await instance.get<any, any>("/proxies");
return (response?.proxies || {}) as Record<string, ApiType.ProxyItem>;
}
/// Get the Proxy information
@@ -151,24 +147,20 @@ export async function getProxies() {
// get proxy providers
export async function getProviders() {
try {
const instance = await getAxios();
const response = await instance.get<any, any>("/providers/proxies");
const instance = await getAxios();
const response = await instance.get<any, any>("/providers/proxies");
const providers = (response.providers || {}) as Record<
string,
ApiType.ProviderItem
>;
const providers = (response.providers || {}) as Record<
string,
ApiType.ProviderItem
>;
return Object.fromEntries(
Object.entries(providers).filter(([key, item]) => {
const type = item.vehicleType.toLowerCase();
return type === "http" || type === "file";
})
);
} catch {
return {};
}
return Object.fromEntries(
Object.entries(providers).filter(([key, item]) => {
const type = item.vehicleType.toLowerCase();
return type === "http" || type === "file";
})
);
}
// proxy providers health check