From 669a1a69538e0db14498dc04e0b5bee7e12747e9 Mon Sep 17 00:00:00 2001 From: MystiPanda Date: Tue, 9 Jan 2024 21:57:06 +0800 Subject: [PATCH] feat: Support URL Scheme for Windows #165 --- src-tauri/Cargo.lock | 263 ++++++++++++++++++++++++++++++++- src-tauri/Cargo.toml | 5 +- src-tauri/src/utils/dirs.rs | 4 +- src-tauri/src/utils/init.rs | 31 ++++ src-tauri/src/utils/resolve.rs | 45 +++++- src-tauri/src/utils/server.rs | 44 +++++- src-tauri/tauri.conf.json | 3 + 7 files changed, 381 insertions(+), 14 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 2e1a4678..cbb8f935 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -101,6 +101,16 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" +[[package]] +name = "async-broadcast" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" +dependencies = [ + "event-listener 2.5.3", + "futures-core", +] + [[package]] name = "async-channel" version = "1.9.0" @@ -238,6 +248,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "async-recursion" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "async-signal" version = "0.2.5" @@ -591,7 +612,6 @@ dependencies = [ "warp", "which 5.0.0", "window-shadows", - "windows-sys 0.52.0", "winreg 0.52.0", ] @@ -1136,6 +1156,27 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enumflags2" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5998b4f30320c9d93aed72f63af821bfdac50465b75428fce77b48ec482c3939" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f95e2801cd355d4a1a3e3953ce6ee5ae9603a5c833455343a8bfe3f44d418246" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -2452,6 +2493,19 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" +[[package]] +name = "mac-notification-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51fca4d74ff9dbaac16a01b924bc3693fa2bba0862c2c633abc73f9a8ea21f64" +dependencies = [ + "cc", + "dirs-next", + "objc-foundation", + "objc_id", + "time", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -2525,6 +2579,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + [[package]] name = "memoffset" version = "0.9.0" @@ -2675,6 +2738,18 @@ dependencies = [ "memoffset 0.6.5", ] +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.7.1", +] + [[package]] name = "nix" version = "0.27.1" @@ -2712,6 +2787,19 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "notify-rust" +version = "4.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "827c5edfa80235ded4ab3fe8e9dc619b4f866ef16fe9b1c6b8a7f8692c0f2226" +dependencies = [ + "log 0.4.20", + "mac-notification-sys", + "serde", + "tauri-winrt-notification", + "zbus", +] + [[package]] name = "ntapi" version = "0.4.1" @@ -2943,6 +3031,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "os_pipe" version = "1.1.4" @@ -3254,7 +3352,7 @@ dependencies = [ "base64 0.21.5", "indexmap 2.1.0", "line-wrap", - "quick-xml", + "quick-xml 0.31.0", "serde", "time", ] @@ -3381,6 +3479,15 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +name = "quick-xml" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +dependencies = [ + "memchr", +] + [[package]] name = "quick-xml" version = "0.31.0" @@ -4307,6 +4414,12 @@ dependencies = [ "loom", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "string_cache" version = "0.8.7" @@ -4535,6 +4648,7 @@ dependencies = [ "ignore", "infer 0.9.0", "minisign-verify", + "notify-rust", "objc", "once_cell", "open 3.2.0", @@ -4710,6 +4824,16 @@ dependencies = [ "toml 0.7.8", ] +[[package]] +name = "tauri-winrt-notification" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "006851c9ccefa3c38a7646b8cec804bb429def3da10497bfa977179869c3e8e2" +dependencies = [ + "quick-xml 0.30.0", + "windows 0.51.1", +] + [[package]] name = "tempfile" version = "3.8.1" @@ -5171,6 +5295,17 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abd2fc5d32b590614af8b0a20d837f32eca055edd0bbead59a9cfe80858be003" +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset 0.9.0", + "tempfile", + "winapi", +] + [[package]] name = "unicase" version = "2.7.0" @@ -5678,6 +5813,16 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" +dependencies = [ + "windows-core 0.51.1", + "windows-targets 0.48.5", +] + [[package]] name = "windows" version = "0.52.0" @@ -6112,6 +6257,16 @@ dependencies = [ "libc", ] +[[package]] +name = "xdg-home" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2769203cd13a0c6015d515be729c526d041e9cf2c0cc478d57faee85f40c6dcd" +dependencies = [ + "nix 0.26.4", + "winapi", +] + [[package]] name = "yaml-rust" version = "0.4.5" @@ -6121,6 +6276,72 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "zbus" +version = "3.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31de390a2d872e4cd04edd71b425e29853f786dc99317ed72d73d6fcf5ebb948" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io 1.13.0", + "async-lock 2.8.0", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "byteorder", + "derivative", + "enumflags2", + "event-listener 2.5.3", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix 0.26.4", + "once_cell", + "ordered-stream", + "rand 0.8.5", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "winapi", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "3.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d1794a946878c0e807f55a397187c11fc7a038ba5d868e7db4f3bd7760bc9d" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "regex 1.10.2", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb80bb776dbda6e23d705cf0123c3b95df99c4ebeaec6c2599d4a5419902b4a9" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + [[package]] name = "zerocopy" version = "0.7.28" @@ -6151,3 +6372,41 @@ dependencies = [ "crc32fast", "crossbeam-utils", ] + +[[package]] +name = "zvariant" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44b291bee0d960c53170780af148dca5fa260a63cdd24f1962fa82e03e53338c" +dependencies = [ + "byteorder", + "enumflags2", + "libc", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "934d7a7dfc310d6ee06c87ffe88ef4eca7d3e37bb251dece2ef93da8f17d8ecd" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index b3911cd1..a06649f7 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -39,13 +39,12 @@ tokio = { version = "1", features = ["full"] } serde = { version = "1.0", features = ["derive"] } reqwest = { version = "0.11", features = ["json", "rustls-tls"] } sysproxy = { git="https://github.com/zzzgydi/sysproxy-rs", branch = "main" } -tauri = { version = "1.5", features = ["icon-png", "clipboard-all", "global-shortcut-all", "process-all", "shell-all", "system-tray", "updater", "window-all"] } +tauri = { version = "1.5", features = [ "notification-all", "icon-png", "clipboard-all", "global-shortcut-all", "process-all", "shell-all", "system-tray", "updater", "window-all"] } [target.'cfg(windows)'.dependencies] runas = "=1.0.0" # 高版本会返回错误 Status deelevate = "0.2.0" -winreg = { version = "0.52", features = ["transactions"] } -windows-sys = { version = "0.52", features = ["Win32_System_LibraryLoader", "Win32_System_SystemInformation"] } +winreg = "0.52.0" [target.'cfg(target_os = "linux")'.dependencies] #openssl diff --git a/src-tauri/src/utils/dirs.rs b/src-tauri/src/utils/dirs.rs index b8f04673..43b406dc 100644 --- a/src-tauri/src/utils/dirs.rs +++ b/src-tauri/src/utils/dirs.rs @@ -8,9 +8,9 @@ use tauri::{ }; #[cfg(not(feature = "verge-dev"))] -static APP_ID: &str = "io.github.clash-verge-rev.clash-verge-rev"; +pub static APP_ID: &str = "io.github.clash-verge-rev.clash-verge-rev"; #[cfg(feature = "verge-dev")] -static APP_ID: &str = "io.github.clash-verge-rev.clash-verge-rev.dev"; +pub static APP_ID: &str = "io.github.clash-verge-rev.clash-verge-rev.dev"; pub static PORTABLE_FLAG: OnceCell = OnceCell::new(); diff --git a/src-tauri/src/utils/init.rs b/src-tauri/src/utils/init.rs index ade2cd93..66ee19b0 100644 --- a/src-tauri/src/utils/init.rs +++ b/src-tauri/src/utils/init.rs @@ -300,3 +300,34 @@ pub fn init_service() -> Result<()> { Ok(()) } + +/// initialize url scheme +#[cfg(target_os = "windows")] +pub fn init_scheme() -> Result<()> { + use tauri::utils::platform::current_exe; + use winreg::enums::*; + use winreg::RegKey; + + let app_exe = current_exe()?; + let app_exe = dunce::canonicalize(app_exe)?; + let app_exe = app_exe.to_string_lossy().to_owned(); + + let hkcu = RegKey::predef(HKEY_CURRENT_USER); + let (clash, _) = hkcu.create_subkey("Software\\Classes\\Clash")?; + clash.set_value("", &"Clash Verge")?; + clash.set_value("URL Protocol", &"Clash Verge URL Scheme Protocol")?; + let (default_icon, _) = hkcu.create_subkey("Software\\Classes\\Clash\\DefaultIcon")?; + default_icon.set_value("", &format!("{app_exe}"))?; + let (command, _) = hkcu.create_subkey("Software\\Classes\\Clash\\Shell\\Open\\Command")?; + command.set_value("", &format!("{app_exe} \"%1\""))?; + + Ok(()) +} +#[cfg(target_os = "linux")] +pub fn init_scheme() -> Result<()> { + Ok(()) +} +#[cfg(target_os = "macos")] +pub fn init_scheme() -> Result<()> { + Ok(()) +} diff --git a/src-tauri/src/utils/resolve.rs b/src-tauri/src/utils/resolve.rs index 1cacc90b..6722b7e4 100644 --- a/src-tauri/src/utils/resolve.rs +++ b/src-tauri/src/utils/resolve.rs @@ -1,10 +1,16 @@ -use crate::config::IVerge; -use crate::{config::Config, core::*, utils::init, utils::server}; +use crate::config::{IVerge, PrfOption}; +use crate::{ + config::{Config, PrfItem}, + core::*, + utils::init, + utils::server, +}; use crate::{log_err, trace_err}; use anyhow::Result; use once_cell::sync::OnceCell; use serde_yaml::Mapping; use std::net::TcpListener; +use tauri::api::notification; use tauri::{App, AppHandle, Manager}; pub static VERSION: OnceCell = OnceCell::new(); @@ -37,6 +43,8 @@ pub fn resolve_setup(app: &mut App) { log_err!(init::init_resources()); #[cfg(target_os = "windows")] log_err!(init::init_service()); + log_err!(init::init_scheme()); + // 处理随机端口 let enable_random_port = Config::verge().latest().enable_random_port.unwrap_or(false); @@ -89,6 +97,13 @@ pub fn resolve_setup(app: &mut App) { log_err!(handle::Handle::update_systray_part()); log_err!(hotkey::Hotkey::global().init(app.app_handle())); log_err!(timer::Timer::global().init()); + + let argvs: Vec = std::env::args().collect(); + if argvs.len() > 1 { + tauri::async_runtime::block_on(async { + resolve_scheme(argvs[1].to_owned()).await; + }); + } } /// reset system proxy @@ -223,3 +238,29 @@ pub fn save_window_size_position(app_handle: &AppHandle, save_to_file: bool) -> Ok(()) } + +pub async fn resolve_scheme(param: String) { + let url = param.trim_start_matches("clash://install-config/?url="); + let option = PrfOption { + user_agent: None, + with_proxy: Some(true), + self_proxy: None, + update_interval: None, + }; + if let Ok(item) = PrfItem::from_url(&url, None, None, Some(option)).await { + if let Ok(_) = Config::profiles().data().append_item(item) { + notification::Notification::new(crate::utils::dirs::APP_ID) + .title("Clash Verge") + .body("Import profile success") + .show() + .unwrap(); + }; + } else { + notification::Notification::new(crate::utils::dirs::APP_ID) + .title("Clash Verge") + .body("Import profile failed") + .show() + .unwrap(); + log::error!("failed to parse url: {}", url); + } +} diff --git a/src-tauri/src/utils/server.rs b/src-tauri/src/utils/server.rs index f4e98368..cd1fb906 100644 --- a/src-tauri/src/utils/server.rs +++ b/src-tauri/src/utils/server.rs @@ -4,19 +4,42 @@ use super::resolve; use crate::config::IVerge; use anyhow::{bail, Result}; use port_scanner::local_port_available; +use std::convert::Infallible; use tauri::AppHandle; use warp::Filter; +#[derive(serde::Deserialize, Debug)] +struct QueryParam { + param: String, +} + /// check whether there is already exists pub fn check_singleton() -> Result<()> { let port = IVerge::get_singleton_port(); if !local_port_available(port) { tauri::async_runtime::block_on(async { - let url = format!("http://127.0.0.1:{port}/commands/visible"); - let resp = reqwest::get(url).await?.text().await?; + let resp = reqwest::get(format!("http://127.0.0.1:{port}/commands/ping")) + .await? + .text() + .await?; if &resp == "ok" { + let argvs: Vec = std::env::args().collect(); + if argvs.len() > 1 { + let param = argvs[1].as_str(); + reqwest::get(format!( + "http://127.0.0.1:{port}/commands/scheme?param={param}" + )) + .await? + .text() + .await?; + } else { + reqwest::get(format!("http://127.0.0.1:{port}/commands/visible")) + .await? + .text() + .await?; + } bail!("app exists"); } @@ -34,11 +57,22 @@ pub fn embed_server(app_handle: AppHandle) { let port = IVerge::get_singleton_port(); tauri::async_runtime::spawn(async move { - let commands = warp::path!("commands" / "visible").map(move || { + let ping = warp::path!("commands" / "ping").map(move || "ok"); + + let visible = warp::path!("commands" / "visible").map(move || { resolve::create_window(&app_handle); - format!("ok") + "ok" }); - warp::serve(commands).bind(([127, 0, 0, 1], port)).await; + let scheme = warp::path!("commands" / "scheme") + .and(warp::query::()) + .and_then(scheme_handler); + + async fn scheme_handler(query: QueryParam) -> Result { + resolve::resolve_scheme(query.param).await; + Ok("ok") + } + let commands = ping.or(visible).or(scheme); + warp::serve(commands).run(([127, 0, 0, 1], port)).await; }); } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 3302abf7..62189b9b 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -51,6 +51,9 @@ }, "clipboard": { "all": true + }, + "notification": { + "all": true } }, "windows": [],