refactor(core): type notice statuses behind the frozen wire contract

This commit is contained in:
Tunglies
2026-10-01 14:34:16 +08:00
committed by GitHub
parent 8ddd99f226
commit 7eb24d8c69
24 changed files with 387 additions and 81 deletions
+2 -1
View File
@@ -1,3 +1,4 @@
use crate::core::notify::NoticeStatus;
use crate::{config::with_encryption, process::AsyncHandler};
use anyhow::{Context as _, Result, anyhow, bail};
use clash_verge_logging::{Type, logging};
@@ -58,7 +59,7 @@ fn parse_mapping(yaml_str: String, path: PathBuf) -> Result<Mapping> {
let error_msg = format!("YAML syntax error in {}: {}", path.display(), err);
logging!(error, Type::Config, "{}", error_msg);
crate::core::handle::Handle::notice_message("config_validate::yaml_syntax_error", &error_msg);
crate::core::handle::Handle::notice(NoticeStatus::ConfigValidateYamlSyntaxError, error_msg.as_str());
bail!("YAML syntax error: {}", err)
}
+1 -1
View File
@@ -8,7 +8,7 @@ pub mod linux;
#[cfg(target_os = "macos")]
pub mod macos_launch_guard;
pub(crate) mod network;
pub mod notification;
pub use crate::core::notify::desktop as notification;
pub mod port;
pub mod resolve;
#[cfg(target_os = "windows")]
-97
View File
@@ -1,97 +0,0 @@
use std::borrow::Cow;
use crate::{core::handle, utils::window_manager::WindowState};
use clash_verge_i18n;
use tauri_plugin_notification::NotificationExt as _;
pub enum NotificationEvent<'a> {
DashboardToggled,
ClashModeChanged {
mode: &'a str,
},
SystemProxyToggled(bool),
/// A proxy change failed while no window could report it.
SystemProxyFailed,
/// The system proxy guard stopped.
SystemProxyGuardStopped,
TunModeToggled(bool),
LightweightModeEntered,
ProfilesReactivated,
AppQuit,
}
/// Whether no app window can currently show the failure.
pub const fn needs_system_notification(state: WindowState) -> bool {
match state {
WindowState::Hidden | WindowState::Minimized | WindowState::NotExist => true,
WindowState::VisibleFocused | WindowState::VisibleUnfocused => false,
}
}
fn notify(title: Cow<'_, str>, body: Cow<'_, str>) {
let app_handle = handle::Handle::app_handle();
app_handle.notification().builder().title(title).body(body).show().ok();
}
pub async fn notify_event<'a>(event: NotificationEvent<'a>) {
match event {
NotificationEvent::DashboardToggled => {
let title = clash_verge_i18n::t!("notifications.dashboardToggled.title");
let body = clash_verge_i18n::t!("notifications.dashboardToggled.body");
notify(title, body);
}
NotificationEvent::ClashModeChanged { mode } => {
let title = clash_verge_i18n::t!("notifications.clashModeChanged.title");
let body = clash_verge_i18n::t!("notifications.clashModeChanged.body")
.replace("{mode}", mode)
.into();
notify(title, body);
}
NotificationEvent::SystemProxyToggled(enabled) => {
let title = clash_verge_i18n::t!("notifications.systemProxyToggled.title");
let key = if enabled {
"notifications.systemProxyToggled.on"
} else {
"notifications.systemProxyToggled.off"
};
let body = clash_verge_i18n::t!(key);
notify(title, body);
}
NotificationEvent::SystemProxyFailed => {
let title = clash_verge_i18n::t!("notifications.systemProxyFailed.title");
let body = clash_verge_i18n::t!("notifications.systemProxyFailed.body");
notify(title, body);
}
NotificationEvent::SystemProxyGuardStopped => {
let title = clash_verge_i18n::t!("notifications.systemProxyGuardStopped.title");
let body = clash_verge_i18n::t!("notifications.systemProxyGuardStopped.body");
notify(title, body);
}
NotificationEvent::TunModeToggled(enabled) => {
let title = clash_verge_i18n::t!("notifications.tunModeToggled.title");
let key = if enabled {
"notifications.tunModeToggled.on"
} else {
"notifications.tunModeToggled.off"
};
let body = clash_verge_i18n::t!(key);
notify(title, body);
}
NotificationEvent::LightweightModeEntered => {
let title = clash_verge_i18n::t!("notifications.lightweightModeEntered.title");
let body = clash_verge_i18n::t!("notifications.lightweightModeEntered.body");
notify(title, body);
}
NotificationEvent::ProfilesReactivated => {
let title = clash_verge_i18n::t!("notifications.profilesReactivated.title");
let body = clash_verge_i18n::t!("notifications.profilesReactivated.body");
notify(title, body);
}
NotificationEvent::AppQuit => {
let title = clash_verge_i18n::t!("notifications.appQuit.title");
let body = clash_verge_i18n::t!("notifications.appQuit.body");
notify(title, body);
}
}
}
+2 -1
View File
@@ -1,3 +1,4 @@
use crate::core::notify::NoticeStatus;
use std::sync::atomic::{AtomicBool, Ordering};
use anyhow::Result;
@@ -223,7 +224,7 @@ async fn init_core_manager() -> bool {
Err(error) => {
logging!(error, Type::Setup, "core manager initialization failed: {error:#}");
CoreManager::global().record_startup_error(CoreFailure::StartFailed(format!("{error:#}")));
Handle::notice_message("core_start::error", "");
Handle::notice(NoticeStatus::CoreStartError, "");
false
}
}
+8 -7
View File
@@ -1,3 +1,4 @@
use crate::core::notify::NoticeStatus;
use anyhow::Result;
use percent_encoding::percent_decode_str;
use smartstring::alias::String;
@@ -93,18 +94,18 @@ async fn import_subscription(url: &str, name: Option<&String>) {
if let Err(e) = profiles::profiles_append_item_safe(&mut item).await {
logging!(error, Type::Config, "failed to import subscription url: {:?}", e);
Config::profiles().await.discard();
handle::Handle::notice_message("import_sub_url::error", e.to_string());
handle::Handle::notice(NoticeStatus::ImportSubUrlError, e.to_string());
return;
}
if let Err(e) = profiles::profiles_save_file_safe().await {
logging!(error, Type::Config, "failed to save imported subscription: {e:#}");
handle::Handle::notice_message("import_sub_url::error", e.to_string());
handle::Handle::notice(NoticeStatus::ImportSubUrlError, e.to_string());
return;
}
logging_error!(Type::Timer, Timer::global().refresh().await);
handle::Handle::notice_message(
"import_sub_url::ok",
handle::Handle::notice(
NoticeStatus::ImportSubUrlOk,
"", // 空 msg 传入,我们不希望导致 后端-前端-后端 死循环,这里只做提醒。
);
@@ -116,7 +117,7 @@ async fn fetch_profile_item(url: &str, name: Option<&String>) -> Option<PrfItem>
Ok(item) => Some(item),
Err(e) => {
logging!(error, Type::Config, "failed to parse profile from url: {:?}", e);
handle::Handle::notice_message("import_sub_url::error", e.to_string());
handle::Handle::notice(NoticeStatus::ImportSubUrlError, e.to_string());
None
}
}
@@ -149,11 +150,11 @@ async fn refresh_core_config() {
Ok(outcome) => {
let message = outcome.to_string();
logging!(warn, Type::Config, "Apply config failed: {}", message);
handle::Handle::notice_message("config_validate::error", message);
handle::Handle::notice(NoticeStatus::ConfigValidateError, message);
}
Err(err) => {
logging!(error, Type::Config, "Apply config error: {err:#}");
handle::Handle::notice_message("update_failed", format!("{err}"));
handle::Handle::notice(NoticeStatus::UpdateFailed, format!("{err}"));
}
}
}