Quellcode durchsuchen

feat: Support URL Scheme for Windows

#165
MystiPanda vor 1 Jahr
Ursprung
Commit
669a1a6953

+ 261 - 2
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",
+]

+ 2 - 3
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

+ 2 - 2
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<bool> = OnceCell::new();
 

+ 31 - 0
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(())
+}

+ 43 - 2
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<String> = 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<String> = 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);
+    }
+}

+ 39 - 5
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<String> = 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::<QueryParam>())
+            .and_then(scheme_handler);
+
+        async fn scheme_handler(query: QueryParam) -> Result<impl warp::Reply, Infallible> {
+            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;
     });
 }

+ 3 - 0
src-tauri/tauri.conf.json

@@ -51,6 +51,9 @@
       },
       "clipboard": {
         "all": true
+      },
+      "notification": {
+        "all": true
       }
     },
     "windows": [],