mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-04-13 13:30:31 +08:00
refactor: single instance and handle deep links
This commit is contained in:
parent
c8aa72186e
commit
d6f9bb4256
17
src-tauri/Cargo.lock
generated
17
src-tauri/Cargo.lock
generated
@ -1171,6 +1171,7 @@ dependencies = [
|
||||
"tauri-plugin-notification",
|
||||
"tauri-plugin-process",
|
||||
"tauri-plugin-shell",
|
||||
"tauri-plugin-single-instance",
|
||||
"tauri-plugin-updater",
|
||||
"tauri-plugin-window-state",
|
||||
"tokio",
|
||||
@ -7775,6 +7776,22 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-single-instance"
|
||||
version = "2.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd707f8c86b4e3004e2c141fa24351f1909ba40ce1b8437e30d5ed5277dd3710"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin-deep-link",
|
||||
"thiserror 2.0.17",
|
||||
"tracing",
|
||||
"windows-sys 0.60.2",
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-updater"
|
||||
version = "2.9.0"
|
||||
|
||||
@ -119,6 +119,7 @@ signal-hook = "0.3.18"
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
tauri-plugin-autostart = "2.5.1"
|
||||
tauri-plugin-global-shortcut = "2.3.1"
|
||||
tauri-plugin-single-instance = { version = "2", features = ["deep-link"] }
|
||||
tauri-plugin-updater = "2.9.0"
|
||||
|
||||
[features]
|
||||
|
||||
@ -20,7 +20,7 @@ use crate::utils::window_manager::WindowManager;
|
||||
use crate::{
|
||||
core::{EventDrivenProxyManager, handle, hotkey},
|
||||
process::AsyncHandler,
|
||||
utils::{resolve, server},
|
||||
utils::resolve,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use config::Config;
|
||||
@ -37,21 +37,24 @@ i18n!("locales", fallback = "zh");
|
||||
pub static APP_HANDLE: OnceCell<AppHandle> = OnceCell::new();
|
||||
/// Application initialization helper functions
|
||||
mod app_init {
|
||||
use super::*;
|
||||
use crate::{module::lightweight, utils::window_manager::WindowManager};
|
||||
|
||||
/// Initialize singleton monitoring for other instances
|
||||
pub fn init_singleton_check() -> Result<()> {
|
||||
tauri::async_runtime::block_on(async move {
|
||||
logging!(info, Type::Setup, "开始检查单例实例...");
|
||||
server::check_singleton().await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
use super::*;
|
||||
|
||||
/// Setup plugins for the Tauri builder
|
||||
pub fn setup_plugins(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
|
||||
#[allow(unused_mut)]
|
||||
let mut builder = builder
|
||||
.plugin(tauri_plugin_single_instance::init(|_app, _args, _cwd| {
|
||||
AsyncHandler::block_on(async move {
|
||||
logging!(info, Type::Window, "检测到从单例模式恢复应用窗口");
|
||||
if !lightweight::exit_lightweight_mode().await {
|
||||
WindowManager::show_main_window().await;
|
||||
} else {
|
||||
logging!(error, Type::Window, "轻量模式退出失败,无法恢复应用窗口");
|
||||
};
|
||||
});
|
||||
}))
|
||||
.plugin(tauri_plugin_notification::init())
|
||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
@ -88,12 +91,6 @@ mod app_init {
|
||||
|
||||
/// Setup deep link handling
|
||||
pub fn setup_deep_links(app: &tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
|
||||
{
|
||||
logging!(info, Type::Setup, "注册深层链接...");
|
||||
app.deep_link().register_all()?;
|
||||
}
|
||||
|
||||
app.deep_link().on_open_url(|event| {
|
||||
let urls = event.urls();
|
||||
AsyncHandler::spawn(move || async move {
|
||||
@ -229,9 +226,10 @@ mod app_init {
|
||||
}
|
||||
|
||||
pub fn run() {
|
||||
if app_init::init_singleton_check().is_err() {
|
||||
return;
|
||||
}
|
||||
// if app_init::init_singleton_check().is_err() {
|
||||
// println!("app exists");
|
||||
// return;
|
||||
// }
|
||||
|
||||
let _ = utils::dirs::init_portable_flag();
|
||||
|
||||
|
||||
@ -462,57 +462,6 @@ pub async fn init_resources() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// initialize url scheme
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn init_scheme() -> Result<()> {
|
||||
use tauri::utils::platform::current_exe;
|
||||
use winreg::{RegKey, enums::*};
|
||||
|
||||
let app_exe = current_exe()?;
|
||||
let app_exe = dunce::canonicalize(app_exe)?;
|
||||
let app_exe = app_exe.to_string_lossy().into_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("", &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<()> {
|
||||
const DESKTOP_FILE: &str = "clash-verge.desktop";
|
||||
|
||||
for scheme in DEEP_LINK_SCHEMES {
|
||||
let handler = format!("x-scheme-handler/{scheme}");
|
||||
let output = std::process::Command::new("xdg-mime")
|
||||
.arg("default")
|
||||
.arg(DESKTOP_FILE)
|
||||
.arg(&handler)
|
||||
.output()?;
|
||||
if !output.status.success() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"failed to set {handler}, {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
crate::utils::linux::ensure_mimeapps_entries(DESKTOP_FILE, DEEP_LINK_SCHEMES)?;
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
pub const fn init_scheme() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
const DEEP_LINK_SCHEMES: &[&str] = &["clash", "clash-verge"];
|
||||
|
||||
pub async fn startup_script() -> Result<()> {
|
||||
let app_handle = handle::Handle::app_handle();
|
||||
let script_path = {
|
||||
|
||||
@ -27,7 +27,6 @@ pub fn resolve_setup_handle() {
|
||||
|
||||
pub fn resolve_setup_sync() {
|
||||
AsyncHandler::spawn(|| async {
|
||||
AsyncHandler::spawn_blocking(init_scheme);
|
||||
AsyncHandler::spawn_blocking(init_embed_server);
|
||||
AsyncHandler::spawn_blocking(init_signal);
|
||||
});
|
||||
@ -89,10 +88,6 @@ pub fn init_handle() {
|
||||
handle::Handle::global().init();
|
||||
}
|
||||
|
||||
pub(super) fn init_scheme() {
|
||||
logging_error!(Type::Setup, init::init_scheme());
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tauri-dev"))]
|
||||
pub(super) async fn resolve_setup_logger() {
|
||||
logging_error!(Type::Setup, init::init_logger().await);
|
||||
|
||||
@ -1,70 +1,18 @@
|
||||
use super::resolve;
|
||||
use crate::{
|
||||
config::{Config, DEFAULT_PAC, IVerge},
|
||||
logging, logging_error,
|
||||
module::lightweight,
|
||||
logging,
|
||||
process::AsyncHandler,
|
||||
utils::{logging::Type, window_manager::WindowManager},
|
||||
utils::logging::Type,
|
||||
};
|
||||
use anyhow::{Result, bail};
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use port_scanner::local_port_available;
|
||||
use reqwest::ClientBuilder;
|
||||
use smartstring::alias::String;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::oneshot;
|
||||
use warp::Filter;
|
||||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct QueryParam {
|
||||
param: String,
|
||||
}
|
||||
|
||||
// 关闭 embedded server 的信号发送端
|
||||
static SHUTDOWN_SENDER: OnceCell<Mutex<Option<oneshot::Sender<()>>>> = OnceCell::new();
|
||||
|
||||
/// check whether there is already exists
|
||||
pub async fn check_singleton() -> Result<()> {
|
||||
let port = IVerge::get_singleton_port();
|
||||
if !local_port_available(port) {
|
||||
let client = ClientBuilder::new()
|
||||
.timeout(Duration::from_millis(500))
|
||||
.build()?;
|
||||
// 需要确保 Send
|
||||
#[allow(clippy::needless_collect)]
|
||||
let argvs: Vec<std::string::String> = std::env::args().collect();
|
||||
if argvs.len() > 1 {
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
let param = argvs[1].as_str();
|
||||
if param.starts_with("clash:") {
|
||||
client
|
||||
.get(format!(
|
||||
"http://127.0.0.1:{port}/commands/scheme?param={param}"
|
||||
))
|
||||
.send()
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
client
|
||||
.get(format!("http://127.0.0.1:{port}/commands/visible"))
|
||||
.send()
|
||||
.await?;
|
||||
}
|
||||
logging!(
|
||||
error,
|
||||
Type::Window,
|
||||
"failed to setup singleton listen server"
|
||||
);
|
||||
bail!("app exists");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The embed server only be used to implement singleton process
|
||||
/// maybe it can be used as pac server later
|
||||
/// The embed server only be used as pac server
|
||||
pub fn embed_server() {
|
||||
let (shutdown_tx, shutdown_rx) = oneshot::channel();
|
||||
#[allow(clippy::expect_used)]
|
||||
@ -74,19 +22,6 @@ pub fn embed_server() {
|
||||
let port = IVerge::get_singleton_port();
|
||||
|
||||
AsyncHandler::spawn(move || async move {
|
||||
let visible = warp::path!("commands" / "visible").and_then(|| async {
|
||||
logging!(info, Type::Window, "检测到从单例模式恢复应用窗口");
|
||||
if !lightweight::exit_lightweight_mode().await {
|
||||
WindowManager::show_main_window().await;
|
||||
} else {
|
||||
logging!(error, Type::Window, "轻量模式退出失败,无法恢复应用窗口");
|
||||
};
|
||||
Ok::<_, warp::Rejection>(warp::reply::with_status::<std::string::String>(
|
||||
"ok".to_string(),
|
||||
warp::http::StatusCode::OK,
|
||||
))
|
||||
});
|
||||
|
||||
let verge_config = Config::verge().await;
|
||||
let clash_config = Config::clash().await;
|
||||
|
||||
@ -109,21 +44,7 @@ pub fn embed_server() {
|
||||
.unwrap_or_default()
|
||||
});
|
||||
|
||||
// Use map instead of and_then to avoid Send issues
|
||||
let scheme = warp::path!("commands" / "scheme")
|
||||
.and(warp::query::<QueryParam>())
|
||||
.and_then(|query: QueryParam| async move {
|
||||
AsyncHandler::spawn(|| async move {
|
||||
logging_error!(Type::Setup, resolve::resolve_scheme(&query.param).await);
|
||||
});
|
||||
Ok::<_, warp::Rejection>(warp::reply::with_status::<std::string::String>(
|
||||
"ok".to_string(),
|
||||
warp::http::StatusCode::OK,
|
||||
))
|
||||
});
|
||||
|
||||
let commands = visible.or(scheme).or(pac);
|
||||
warp::serve(commands)
|
||||
warp::serve(pac)
|
||||
.bind(([127, 0, 0, 1], port))
|
||||
.await
|
||||
.graceful(async {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user