feat(app): upgrade to tauri 3.0.0-alpha

Move the app onto the tauri v3 alpha line: runtime selection is now
explicit via tauri-runtime-wry, the Wry generics give way to the
DynRuntime defaults, and every official plugin rides 3.0.0-alpha.0
alongside the matching npm packages; pnpm moves to 12.4.2. The
tray-icon patch is dropped because upstream 0.25.1 ships the macOS 27
click-reentrancy fix. perf-harness is temporarily hollowed out until
tauri-plugin-wdio-webdriver publishes a tauri 3 release.
This commit is contained in:
Tunglies
2026-09-16 17:41:52 +08:00
parent e385d37e5b
commit 83a06e5716
12 changed files with 501 additions and 671 deletions
+19 -18
View File
@@ -23,13 +23,15 @@ tauri-dev = ["dep:tauri-plugin-devtools"]
tokio-trace = ["clash-verge-logging/tokio-trace"]
chrome-trace = ["clash-verge-logging/chrome-trace"]
clippy = ["tauri/test"]
perf-harness = ["dep:tauri-plugin-wdio-webdriver"]
# The perf-harness feature and its main.rs wiring are parked until
# tauri-plugin-wdio-webdriver publishes a tauri 3 release; restore both
# from git history together with the crate dependency.
[package.metadata.bundle]
identifier = "io.github.clash-verge-rev.clash-verge-rev"
[build-dependencies]
tauri-build = { version = "2.6.3", features = [] }
tauri-build = { version = "3.0.0-alpha.0", features = [] }
[dependencies]
clash-verge-draft = { workspace = true }
@@ -42,12 +44,12 @@ tauri-plugin-clash-verge-sysinfo = { workspace = true }
tauri-plugin-clipboard-manager = { workspace = true }
tauri = { workspace = true, features = [
"protocol-asset",
"devtools",
"tracing",
"tray-icon",
"image-ico",
"image-png",
] }
tauri-runtime-wry = { workspace = true, features = ["devtools"] }
parking_lot = { workspace = true }
anyhow = { workspace = true }
tokio = { workspace = true }
@@ -74,13 +76,13 @@ sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs.git", branch
"guard",
] }
network-interface = { version = "2.0.5", features = ["serde"] }
tauri-plugin-shell = "2.3.6"
tauri-plugin-opener = "2.5.5"
tauri-plugin-dialog = "2.7.3"
tauri-plugin-fs = "2.5.2"
tauri-plugin-process = "2.3.1"
tauri-plugin-deep-link = "2.4.10"
tauri-plugin-window-state = "2.4.1"
tauri-plugin-shell = "3.0.0-alpha.0"
tauri-plugin-opener = "3.0.0-alpha.0"
tauri-plugin-dialog = "3.0.0-alpha.0"
tauri-plugin-fs = "3.0.0-alpha.0"
tauri-plugin-process = "3.0.0-alpha.0"
tauri-plugin-deep-link = "3.0.0-alpha.0"
tauri-plugin-window-state = "3.0.0-alpha.0"
zip = "8.6.0"
flate2 = "1.1.10"
reqwest_dav = "0.3.3"
@@ -91,13 +93,13 @@ getrandom = "0.4.3"
futures = "0.3.34"
gethostname = "1.1.0"
scopeguard = "1.2.0"
tauri-plugin-notification = "2.4.0"
tauri-plugin-notification = "3.0.0-alpha.0"
tokio-stream = "0.1.19"
backon = { version = "1.6.0", features = ["tokio-sleep"] }
tauri-plugin-http = "2.6.0"
tauri-plugin-http = "3.0.0-alpha.0"
tracing = { workspace = true }
tauri-plugin-devtools = { version = "2.2.1", optional = true }
tauri-plugin-mihomo = { git = "https://github.com/clash-verge-rev/tauri-plugin-mihomo", branch = "main" }
tauri-plugin-devtools = { version = "3.0.0-alpha.0", optional = true }
tauri-plugin-mihomo = { git = "https://github.com/clash-verge-rev/tauri-plugin-mihomo", branch = "feat/tauri-v3" }
async-trait = "0.1.92"
clash_verge_service_ipc = { git = "https://github.com/clash-verge-rev/clash-verge-service-ipc.git", tag = "v2.7.1", version = "2.7.1", features = [
"client",
@@ -114,7 +116,6 @@ tokio-util = { version = "0.7.19", features = ["time"] }
libc = "0.2.189"
[target.'cfg(target_os = "macos")'.dependencies]
tauri-plugin-wdio-webdriver = { version = "=1.4.0", optional = true }
objc2 = "0.6.4"
objc2-foundation = { version = "0.3.2", features = [
"NSString",
@@ -146,9 +147,9 @@ windows = { version = "0.62.2", features = ["Win32_Globalization"] }
windows-sys.workspace = true
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-autostart = "2.5.1"
tauri-plugin-global-shortcut = "2.3.2"
tauri-plugin-updater = "2.11.0"
tauri-plugin-autostart = "3.0.0-alpha.0"
tauri-plugin-global-shortcut = "3.0.0-alpha.0"
tauri-plugin-updater = "3.0.0-alpha.0"
[dev-dependencies]
criterion = { workspace = true }
-1
View File
@@ -70,7 +70,6 @@
"dialog:allow-open",
"global-shortcut:allow-is-registered",
"global-shortcut:allow-register",
"global-shortcut:allow-register-all",
"global-shortcut:allow-unregister",
"global-shortcut:allow-unregister-all",
"process:allow-exit",
+34 -32
View File
@@ -24,7 +24,7 @@ use std::borrow::Cow;
use std::collections::HashMap;
use std::time::Duration;
use tauri::{
AppHandle, Wry,
AppHandle, DynRuntime,
menu::{CheckMenuItem, IsMenuItem, MenuEvent, MenuItem, PredefinedMenuItem, Submenu},
};
@@ -35,7 +35,7 @@ use menu_def::{MenuIds, MenuTexts};
// TODO: 是否需要将可变菜单抽离存储起来,后续直接更新对应菜单实例,无需重新创建菜单(待考虑)
type ProxyMenuItem = (Option<Submenu<Wry>>, Vec<Box<dyn IsMenuItem<Wry>>>);
type ProxyMenuItem = (Option<Submenu<DynRuntime>>, Vec<Box<dyn IsMenuItem<DynRuntime>>>);
const TRAY_CLICK_DEBOUNCE_MS: u64 = 300;
pub const TRAY_ID: &str = "clash-verge-rev-tray";
@@ -440,7 +440,7 @@ fn create_hotkeys(hotkeys: &Option<Vec<String>>) -> HashMap<&str, &str> {
fn create_profile_menu_item(
app_handle: &AppHandle,
profiles_preview: Vec<IProfilePreview<'_>>,
) -> Result<Vec<CheckMenuItem<Wry>>> {
) -> Result<Vec<CheckMenuItem<DynRuntime>>> {
profiles_preview
.into_iter()
.map(|profile| {
@@ -462,9 +462,9 @@ fn create_subcreate_proxy_menu_item(
proxy_mode: &str,
proxy_group_order_map: Option<HashMap<String, usize>>,
proxy_nodes_data: Option<Proxies>,
) -> Vec<Submenu<Wry>> {
let proxy_submenus: Vec<Submenu<Wry>> = {
let mut submenus: Vec<(String, usize, Submenu<Wry>)> = Vec::new();
) -> Vec<Submenu<DynRuntime>> {
let proxy_submenus: Vec<Submenu<DynRuntime>> = {
let mut submenus: Vec<(String, usize, Submenu<DynRuntime>)> = Vec::new();
// TODO: 应用启动时,内核还未启动完全,无法获取代理节点信息
if let Some(proxy_nodes_data) = proxy_nodes_data {
@@ -484,7 +484,7 @@ fn create_subcreate_proxy_menu_item(
let now_proxy = group_data.now.as_deref().unwrap_or_default();
let group_items: Vec<CheckMenuItem<Wry>> = all_proxies
let group_items: Vec<CheckMenuItem<DynRuntime>> = all_proxies
.iter()
.filter_map(|proxy_str| {
let is_selected = *proxy_str == now_proxy;
@@ -515,8 +515,10 @@ fn create_subcreate_proxy_menu_item(
let group_display_name = group_name.to_string();
let group_items_refs: Vec<&dyn IsMenuItem<Wry>> =
group_items.iter().map(|item| item as &dyn IsMenuItem<Wry>).collect();
let group_items_refs: Vec<&dyn IsMenuItem<DynRuntime>> = group_items
.iter()
.map(|item| item as &dyn IsMenuItem<DynRuntime>)
.collect();
if let Ok(submenu) = Submenu::with_id_and_items(
app_handle,
@@ -552,7 +554,7 @@ fn create_subcreate_proxy_menu_item(
fn create_proxy_menu_item(
app_handle: &AppHandle,
show_proxy_groups_inline: bool,
proxy_submenus: Vec<Submenu<Wry>>,
proxy_submenus: Vec<Submenu<DynRuntime>>,
proxies_text: &str,
) -> Result<ProxyMenuItem> {
let (proxies_submenu, inline_proxy_items) = if show_proxy_groups_inline {
@@ -560,13 +562,13 @@ fn create_proxy_menu_item(
None,
proxy_submenus
.into_iter()
.map(|submenu| Box::new(submenu) as Box<dyn IsMenuItem<Wry>>)
.map(|submenu| Box::new(submenu) as Box<dyn IsMenuItem<DynRuntime>>)
.collect(),
)
} else if !proxy_submenus.is_empty() {
let proxy_submenu_refs: Vec<&dyn IsMenuItem<Wry>> = proxy_submenus
let proxy_submenu_refs: Vec<&dyn IsMenuItem<DynRuntime>> = proxy_submenus
.iter()
.map(|submenu| submenu as &dyn IsMenuItem<Wry>)
.map(|submenu| submenu as &dyn IsMenuItem<DynRuntime>)
.collect();
(
@@ -593,7 +595,7 @@ async fn create_tray_menu(
tun_mode_available: bool,
profiles_preview: Vec<IProfilePreview<'_>>,
options: TrayMenuOptions,
) -> Result<tauri::menu::Menu<Wry>> {
) -> Result<tauri::menu::Menu<DynRuntime>> {
let current_proxy_mode = mode.unwrap_or("");
let mut verge_settings = Config::verge().await.latest_arc();
@@ -647,12 +649,12 @@ async fn create_tray_menu(
let hotkeys = create_hotkeys(&verge_settings.hotkeys);
let profile_menu_items: Vec<CheckMenuItem<Wry>> = create_profile_menu_item(app_handle, profiles_preview)?;
let profile_menu_items: Vec<CheckMenuItem<DynRuntime>> = create_profile_menu_item(app_handle, profiles_preview)?;
let texts = MenuTexts::new();
let profile_menu_items_refs: Vec<&dyn IsMenuItem<Wry>> = profile_menu_items
let profile_menu_items_refs: Vec<&dyn IsMenuItem<DynRuntime>> = profile_menu_items
.iter()
.map(|item| item as &dyn IsMenuItem<Wry>)
.map(|item| item as &dyn IsMenuItem<DynRuntime>)
.collect();
let open_window = &MenuItem::with_id(
@@ -705,9 +707,9 @@ async fn create_tray_menu(
outbound_modes_label.as_str(),
true,
&[
rule_mode as &dyn IsMenuItem<Wry>,
global_mode as &dyn IsMenuItem<Wry>,
direct_mode as &dyn IsMenuItem<Wry>,
rule_mode as &dyn IsMenuItem<DynRuntime>,
global_mode as &dyn IsMenuItem<DynRuntime>,
direct_mode as &dyn IsMenuItem<DynRuntime>,
],
)?)
};
@@ -812,7 +814,7 @@ async fn create_tray_menu(
&texts.more,
true,
&[
copy_env as &dyn IsMenuItem<Wry>,
copy_env as &dyn IsMenuItem<DynRuntime>,
close_all_connections,
restart_clash,
restart_app,
@@ -829,13 +831,13 @@ async fn create_tray_menu(
let separator = &PredefinedMenuItem::separator(app_handle)?;
let mut menu_items: Vec<&dyn IsMenuItem<Wry>> = vec![open_window, separator];
let mut menu_items: Vec<&dyn IsMenuItem<DynRuntime>> = vec![open_window, separator];
if show_outbound_modes_inline {
menu_items.extend_from_slice(&[
rule_mode as &dyn IsMenuItem<Wry>,
global_mode as &dyn IsMenuItem<Wry>,
direct_mode as &dyn IsMenuItem<Wry>,
rule_mode as &dyn IsMenuItem<DynRuntime>,
global_mode as &dyn IsMenuItem<DynRuntime>,
direct_mode as &dyn IsMenuItem<DynRuntime>,
]);
} else if let Some(ref outbound_modes) = outbound_modes {
menu_items.push(outbound_modes);
@@ -845,7 +847,7 @@ async fn create_tray_menu(
match tray_proxy_groups_display_mode {
"default" => {
menu_items.extend(proxies_menu.iter().map(|item| item as &dyn IsMenuItem<_>));
menu_items.extend(proxies_menu.iter().map(|item| item as &dyn IsMenuItem<DynRuntime>));
}
"inline" if !inline_proxy_items.is_empty() => {
menu_items.extend(inline_proxy_items.iter().map(|item| item.as_ref()));
@@ -855,14 +857,14 @@ async fn create_tray_menu(
menu_items.extend_from_slice(&[
separator,
system_proxy as &dyn IsMenuItem<Wry>,
tun_mode as &dyn IsMenuItem<Wry>,
system_proxy as &dyn IsMenuItem<DynRuntime>,
tun_mode as &dyn IsMenuItem<DynRuntime>,
separator,
lightweight_mode as &dyn IsMenuItem<Wry>,
open_dir as &dyn IsMenuItem<Wry>,
more as &dyn IsMenuItem<Wry>,
lightweight_mode as &dyn IsMenuItem<DynRuntime>,
open_dir as &dyn IsMenuItem<DynRuntime>,
more as &dyn IsMenuItem<DynRuntime>,
separator,
quit as &dyn IsMenuItem<Wry>,
quit as &dyn IsMenuItem<DynRuntime>,
]);
let menu = tauri::menu::MenuBuilder::new(app_handle).items(&menu_items).build()?;
+3 -3
View File
@@ -39,7 +39,7 @@ mod app_init {
}
/// Setup plugins for the Tauri builder
pub fn setup_plugins(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
pub fn setup_plugins(builder: tauri::Builder) -> tauri::Builder {
#[allow(unused_mut)]
let mut builder = builder
.plugin(tauri_plugin_clash_verge_sysinfo::init())
@@ -116,7 +116,7 @@ mod app_init {
Ok(())
}
pub fn generate_handlers() -> impl Fn(tauri::ipc::Invoke<tauri::Wry>) -> bool + Send + Sync + 'static {
pub fn generate_handlers() -> impl Fn(tauri::ipc::Invoke) -> bool + Send + Sync + 'static {
tauri::generate_handler![
tauri_plugin_clash_verge_sysinfo::commands::get_system_info,
tauri_plugin_clash_verge_sysinfo::commands::get_app_uptime,
@@ -255,7 +255,7 @@ pub fn run() -> std::process::ExitCode {
#[cfg(target_os = "linux")]
utils::linux::workarounds::apply_wayland_webkit_fix();
let builder = app_init::setup_plugins(tauri::Builder::default())
let builder = app_init::setup_plugins(tauri::Builder::default().runtime(tauri_runtime_wry::Wry::default()))
.setup(|app| {
// Logger may not be ready yet, so mirror setup panics to stderr.
fn log_setup_panic(stage: &str, panic: Box<dyn std::any::Any + Send>) {
-11
View File
@@ -1,19 +1,8 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use std::process::ExitCode;
#[cfg(not(all(feature = "perf-harness", target_os = "macos")))]
use std::sync::atomic::{AtomicUsize, Ordering};
#[cfg(all(feature = "perf-harness", target_os = "macos"))]
#[path = "../../scripts/perf/app.rs"]
mod perf;
#[cfg(all(feature = "perf-harness", target_os = "macos"))]
fn main() -> ExitCode {
perf::run()
}
#[cfg(not(all(feature = "perf-harness", target_os = "macos")))]
fn main() -> ExitCode {
let default_parallelism = std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1);
let worker_limit = std::cmp::min(default_parallelism, 8);
+4 -1
View File
@@ -132,7 +132,10 @@ pub fn take_webview_needs_reload() -> bool {
/// reload visible webviews immediately, and defer a hidden main-window reload until activation.
/// Registering this callback replaces Tauri's default automatic reload.
#[cfg(target_os = "macos")]
pub fn on_web_content_process_terminated(webview: &tauri::Webview) {
pub fn on_web_content_process_terminated(
webview: &tauri::Webview,
_termination: &tauri::webview::WebContentProcessTermination,
) {
if handle::Handle::global().is_exiting() {
return;
}
+9 -9
View File
@@ -4,7 +4,7 @@ use clash_verge_logging::{Type, logging};
use once_cell::sync::Lazy;
use std::pin::Pin;
use std::time::Duration;
use tauri::{Manager as _, WebviewWindow, Wry};
use tauri::{Manager as _, WebviewWindow};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum WindowOperationResult {
@@ -50,7 +50,7 @@ impl WindowManager {
handle::Handle::global().set_activation_policy_regular();
}
fn get_main_window_with_state() -> (Option<WebviewWindow<Wry>>, WindowState) {
fn get_main_window_with_state() -> (Option<WebviewWindow>, WindowState) {
let Some(window) = Self::get_main_window() else {
return (None, WindowState::NotExist);
};
@@ -76,7 +76,7 @@ impl WindowManager {
Self::get_main_window_with_state().1
}
pub fn get_main_window() -> Option<WebviewWindow<Wry>> {
pub fn get_main_window() -> Option<WebviewWindow> {
let app_handle = handle::Handle::app_handle();
app_handle.get_webview_window("main")
}
@@ -147,7 +147,7 @@ impl WindowManager {
}
}
fn hide_main_window(window: Option<&WebviewWindow<Wry>>) -> WindowOperationResult {
fn hide_main_window(window: Option<&WebviewWindow>) -> WindowOperationResult {
logging!(debug, Type::Window, "窗口可见,将隐藏窗口");
if let Some(window) = window {
match window.close() {
@@ -166,7 +166,7 @@ impl WindowManager {
}
}
fn activate_existing_main_window(window: Option<&WebviewWindow<Wry>>) -> WindowOperationResult {
fn activate_existing_main_window(window: Option<&WebviewWindow>) -> WindowOperationResult {
logging!(debug, Type::Window, "窗口存在但被隐藏或最小化,将激活窗口");
if let Some(window) = window {
Self::activate_window(window)
@@ -176,7 +176,7 @@ impl WindowManager {
}
}
fn activate_window(window: &WebviewWindow<Wry>) -> WindowOperationResult {
fn activate_window(window: &WebviewWindow) -> WindowOperationResult {
logging!(debug, Type::Window, "开始激活窗口");
#[cfg(target_os = "macos")]
Self::set_macos_activation_policy_regular();
@@ -233,15 +233,15 @@ impl WindowManager {
}
}
pub fn is_main_window_visible(window: Option<&WebviewWindow<Wry>>) -> bool {
pub fn is_main_window_visible(window: Option<&WebviewWindow>) -> bool {
window.map(|w| w.is_visible().unwrap_or(false)).unwrap_or(false)
}
pub fn is_main_window_focused(window: Option<&WebviewWindow<Wry>>) -> bool {
pub fn is_main_window_focused(window: Option<&WebviewWindow>) -> bool {
window.map(|w| w.is_focused().unwrap_or(false)).unwrap_or(false)
}
fn is_main_window_minimized(window: Option<&WebviewWindow<Wry>>) -> bool {
fn is_main_window_minimized(window: Option<&WebviewWindow>) -> bool {
window.map(|w| w.is_minimized().unwrap_or(false)).unwrap_or(false)
}