mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-09 11:52:08 +08:00
refactor(backend): specta ipc bindings (#8195)
* refactor(ipc): generate bindings with Specta * fix(profile): project IPC inputs to consumed fields * fix(events): remove unused refresh payloads * fix(webdav): return only consumed backup metadata * fix(network): trim unused interface metadata * fix(sysinfo): return only displayed system fields * chore(knip): check generated runtime exports * fix(proxy): confirm runtime chain updates before UI changes * fix(clipboard): propagate proxy environment copy failures * refactor(dns): remove ignored runtime patch * refactor(core): trim unused upgrade response fields * refactor(ipc): avoid redundant response copies * refactor(proxy): clarify automatic proxy query naming * chore(tests): remove added IPC regression tests * refactor(ipc): enforce typed command boundaries * chore(contract): check generated bindings before push * refactor(ipc): remove unused proxy liveness payloads
This commit is contained in:
@@ -1,8 +1,12 @@
|
||||
//! Prints the frontend wire contract as JSON to stdout. Zero new dependencies:
|
||||
//! the payload is derived from the same serializers the app uses at runtime.
|
||||
//! Consumed by scripts/gen-contract.mjs; CI regenerates and diffs the committed
|
||||
//! TypeScript artifact.
|
||||
|
||||
fn main() {
|
||||
println!("{}", app_lib::frontend_wire_contract());
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut args = std::env::args_os().skip(1);
|
||||
let output = args
|
||||
.next()
|
||||
.map(std::path::PathBuf::from)
|
||||
.unwrap_or_else(|| std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../src/services/bindings.ts"));
|
||||
if args.next().is_some() {
|
||||
return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "expected at most one output path").into());
|
||||
}
|
||||
app_lib::ipc::builder().export(specta_typescript::Typescript::default(), output)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -9,12 +9,14 @@ use smartstring::alias::String;
|
||||
use tauri::{AppHandle, Manager as _, ipc::Channel};
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn open_app_dir() -> CmdResult<()> {
|
||||
let app_dir = dirs::app_home_dir().stringify_err()?;
|
||||
open::that(app_dir).stringify_err()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn open_core_dir() -> CmdResult<()> {
|
||||
let core_dir = tauri::utils::platform::current_exe().stringify_err()?;
|
||||
let core_dir = core_dir.parent().ok_or("failed to get core dir")?;
|
||||
@@ -22,12 +24,14 @@ pub async fn open_core_dir() -> CmdResult<()> {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn open_logs_dir() -> CmdResult<()> {
|
||||
let log_dir = dirs::app_logs_dir().stringify_err()?;
|
||||
open::that(log_dir).stringify_err()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn open_devtools(app_handle: AppHandle) {
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
if !window.is_devtools_open() {
|
||||
@@ -39,22 +43,26 @@ pub fn open_devtools(app_handle: AppHandle) {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn exit_app() {
|
||||
feat::quit().await;
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn restart_app() -> CmdResult<()> {
|
||||
feat::restart_app().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn install_update(
|
||||
app_handle: AppHandle,
|
||||
version: String,
|
||||
version: std::string::String,
|
||||
on_event: Channel<DownloadEvent>,
|
||||
) -> CmdResult<bool> {
|
||||
let version: String = version.into();
|
||||
SilentUpdater::global()
|
||||
.install_update(&app_handle, &version, |event| {
|
||||
let _ = on_event.send(event);
|
||||
@@ -64,22 +72,32 @@ pub async fn install_update(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn cancel_update_download() {
|
||||
SilentUpdater::global().cancel_download();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_app_dir() -> CmdResult<String> {
|
||||
#[specta::specta]
|
||||
pub fn get_app_dir() -> CmdResult<std::string::String> {
|
||||
let app_home_dir = dirs::app_home_dir().stringify_err()?.to_string_lossy().into();
|
||||
Ok(app_home_dir)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String> {
|
||||
feat::download_icon_cache(url, name).await
|
||||
#[specta::specta]
|
||||
pub async fn download_icon_cache(
|
||||
url: std::string::String,
|
||||
name: std::string::String,
|
||||
) -> CmdResult<std::string::String> {
|
||||
let url: String = url.into();
|
||||
let name: String = name.into();
|
||||
feat::download_icon_cache(url, name).await.map(Into::into)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn copy_icon_file(path: String, icon_info: feat::IconInfo) -> CmdResult<String> {
|
||||
feat::copy_icon_file(path, icon_info).await
|
||||
#[specta::specta]
|
||||
pub async fn copy_icon_file(path: std::string::String, icon_info: feat::IconInfo) -> CmdResult<std::string::String> {
|
||||
let path: String = path.into();
|
||||
feat::copy_icon_file(path, icon_info).await.map(Into::into)
|
||||
}
|
||||
|
||||
@@ -4,33 +4,44 @@ use feat::LocalBackupFile;
|
||||
use smartstring::alias::String;
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn create_local_backup() -> CmdResult<()> {
|
||||
feat::create_local_backup().await.stringify_err()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn list_local_backup() -> CmdResult<Vec<LocalBackupFile>> {
|
||||
feat::list_local_backup().await.stringify_err()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn delete_local_backup(filename: String) -> CmdResult<()> {
|
||||
#[specta::specta]
|
||||
pub async fn delete_local_backup(filename: std::string::String) -> CmdResult<()> {
|
||||
let filename: String = filename.into();
|
||||
feat::delete_local_backup(filename).await.stringify_err()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn restore_local_backup(filename: String) -> CmdResult<()> {
|
||||
#[specta::specta]
|
||||
pub async fn restore_local_backup(filename: std::string::String) -> CmdResult<()> {
|
||||
let filename: String = filename.into();
|
||||
feat::restore_local_backup(filename)
|
||||
.await
|
||||
.map_err(|error| super::proxy_aware_error(&error))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn import_local_backup(source: String) -> CmdResult<String> {
|
||||
feat::import_local_backup(source).await.stringify_err()
|
||||
#[specta::specta]
|
||||
pub async fn import_local_backup(source: std::string::String) -> CmdResult<std::string::String> {
|
||||
let source: String = source.into();
|
||||
feat::import_local_backup(source).await.map(Into::into).stringify_err()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn export_local_backup(filename: String, destination: String) -> CmdResult<()> {
|
||||
#[specta::specta]
|
||||
pub async fn export_local_backup(filename: std::string::String, destination: std::string::String) -> CmdResult<()> {
|
||||
let filename: String = filename.into();
|
||||
let destination: String = destination.into();
|
||||
feat::export_local_backup(filename, destination).await.stringify_err()
|
||||
}
|
||||
|
||||
+36
-24
@@ -12,30 +12,33 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use clash_verge_logging::{Type, logging, logging_error};
|
||||
use serde_yaml_ng::Mapping;
|
||||
use smartstring::alias::String;
|
||||
use tokio::fs;
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn copy_clash_env() -> CmdResult {
|
||||
feat::copy_clash_env().await;
|
||||
Ok(())
|
||||
feat::copy_clash_env().await.stringify_err()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn get_clash_info() -> CmdResult<ClashInfo> {
|
||||
Ok(Config::clash().await.data_arc().get_client_info())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn patch_clash_config(payload: Mapping) -> CmdResult {
|
||||
feat::patch_clash(&payload)
|
||||
#[specta::specta]
|
||||
pub async fn patch_clash_config(payload: crate::ipc::YamlMapping) -> CmdResult {
|
||||
feat::patch_clash(&payload.0)
|
||||
.await
|
||||
.map_err(|error| proxy_aware_coded_error(&error, "CLASH_CONFIG_UPDATE_FAILED"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn patch_clash_mode(payload: String) -> CmdResult {
|
||||
#[specta::specta]
|
||||
pub async fn patch_clash_mode(payload: std::string::String) -> CmdResult {
|
||||
let payload: String = payload.into();
|
||||
feat::change_clash_mode(payload)
|
||||
.await
|
||||
.with_error_code("CLASH_MODE_UPDATE_FAILED")
|
||||
@@ -43,12 +46,15 @@ pub async fn patch_clash_mode(payload: String) -> CmdResult {
|
||||
|
||||
/// Reads the saved mode without depending on strict mihomo `/configs` deserialization.
|
||||
#[tauri::command]
|
||||
pub async fn get_clash_mode() -> CmdResult<Option<String>> {
|
||||
Ok(Config::clash().await.data_arc().get_mode().map(Into::into))
|
||||
#[specta::specta]
|
||||
pub async fn get_clash_mode() -> CmdResult<Option<std::string::String>> {
|
||||
Ok(Config::clash().await.data_arc().get_mode())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn change_clash_core(clash_core: String) -> CmdResult<Option<CommandFailure>> {
|
||||
#[specta::specta]
|
||||
pub async fn change_clash_core(clash_core: std::string::String) -> CmdResult<Option<CommandFailure>> {
|
||||
let clash_core: String = clash_core.into();
|
||||
logging!(info, Type::Config, "changing core to {clash_core}");
|
||||
|
||||
match CoreManager::global().change_core(&clash_core).await {
|
||||
@@ -80,6 +86,7 @@ pub async fn change_clash_core(clash_core: String) -> CmdResult<Option<CommandFa
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn restart_core() -> CmdResult {
|
||||
logging_error!(Type::Core, profiles_save_file_safe().await);
|
||||
let result = CoreManager::global()
|
||||
@@ -94,6 +101,7 @@ pub async fn restart_core() -> CmdResult {
|
||||
|
||||
/// Replaces the managed core binary atomically instead of letting mihomo overwrite itself.
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn upgrade_clash_core(force: bool) -> CmdResult<feat::CoreUpgradeReport> {
|
||||
let report = feat::upgrade_core(force).await.with_error_code("CORE_UPGRADE_FAILED")?;
|
||||
if report.upgraded {
|
||||
@@ -103,7 +111,9 @@ pub async fn upgrade_clash_core(force: bool) -> CmdResult<feat::CoreUpgradeRepor
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn test_delay(url: String) -> CmdResult<u32> {
|
||||
#[specta::specta]
|
||||
pub async fn test_delay(url: std::string::String) -> CmdResult<u32> {
|
||||
let url: String = url.into();
|
||||
let result = match feat::test_delay(url).await {
|
||||
Ok(delay) => delay,
|
||||
Err(e) => {
|
||||
@@ -115,13 +125,14 @@ pub async fn test_delay(url: String) -> CmdResult<u32> {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn save_dns_config(dns_config: Mapping) -> CmdResult {
|
||||
#[specta::specta]
|
||||
pub async fn save_dns_config(dns_config: crate::ipc::YamlMapping) -> CmdResult {
|
||||
use crate::utils::dirs;
|
||||
use tokio::fs;
|
||||
|
||||
let dns_path = dirs::app_home_dir().stringify_err()?.join(constants::files::DNS_CONFIG);
|
||||
|
||||
let yaml_str = yaml_emitter::to_mihomo_config_string(&dns_config).stringify_err()?;
|
||||
let yaml_str = yaml_emitter::to_mihomo_config_string(&dns_config.0).stringify_err()?;
|
||||
fs::write(&dns_path, yaml_str).await.stringify_err()?;
|
||||
logging!(info, Type::Config, "DNS config saved to {dns_path:?}");
|
||||
|
||||
@@ -129,22 +140,27 @@ pub async fn save_dns_config(dns_config: Mapping) -> CmdResult {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn take_dns_override_notice() -> bool {
|
||||
crate::config::dns::take_dns_override_notice()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn set_dns_override(
|
||||
profile_uid: String,
|
||||
profile_uid: std::string::String,
|
||||
enabled: bool,
|
||||
confirmation: Option<String>,
|
||||
confirmation: Option<std::string::String>,
|
||||
) -> CmdResult<feat::DnsOverrideOutcome> {
|
||||
let profile_uid: String = profile_uid.into();
|
||||
let confirmation = confirmation.map(String::from);
|
||||
feat::set_dns_override(profile_uid, enabled, confirmation)
|
||||
.await
|
||||
.map_err(|error| proxy_aware_coded_error(&error, "DNS_OVERRIDE_UPDATE_FAILED"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn apply_dns_config(apply: bool) -> CmdResult {
|
||||
if apply {
|
||||
let dns_path = dirs::app_home_dir().stringify_err()?.join(constants::files::DNS_CONFIG);
|
||||
@@ -163,7 +179,7 @@ pub async fn apply_dns_config(apply: bool) -> CmdResult {
|
||||
);
|
||||
})?;
|
||||
|
||||
let patch_config = serde_yaml_ng::from_str::<serde_yaml_ng::Mapping>(&dns_yaml).stringify_err_log(|e| {
|
||||
serde_yaml_ng::from_str::<serde_yaml_ng::Mapping>(&dns_yaml).stringify_err_log(|e| {
|
||||
logging!(
|
||||
error,
|
||||
Type::Config,
|
||||
@@ -172,13 +188,6 @@ pub async fn apply_dns_config(apply: bool) -> CmdResult {
|
||||
);
|
||||
})?;
|
||||
|
||||
let mut patch = serde_yaml_ng::Mapping::new();
|
||||
patch.insert("dns".into(), patch_config.into());
|
||||
|
||||
Config::runtime().await.edit_draft(|d| {
|
||||
d.patch_config(&patch);
|
||||
});
|
||||
|
||||
CoreManager::global().update_config_checked().await.map_err(|error| {
|
||||
logging!(error, Type::Config, "failed to apply the config with DNS: {error:#}");
|
||||
proxy_aware_error(&error)
|
||||
@@ -202,20 +211,22 @@ pub async fn apply_dns_config(apply: bool) -> CmdResult {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_dns_config_content() -> CmdResult<Option<String>> {
|
||||
#[specta::specta]
|
||||
pub async fn get_dns_config_content() -> CmdResult<Option<std::string::String>> {
|
||||
use crate::utils::dirs;
|
||||
use tokio::fs;
|
||||
|
||||
let dns_path = dirs::app_home_dir().stringify_err()?.join(constants::files::DNS_CONFIG);
|
||||
|
||||
match fs::read_to_string(&dns_path).await {
|
||||
Ok(content) => Ok(Some(content.into())),
|
||||
Ok(content) => Ok(Some(content)),
|
||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(None),
|
||||
Err(error) => Err(CommandFailure::plain(error)),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn validate_dns_config() -> CmdResult<ValidationOutcome> {
|
||||
let app_dir = dirs::app_home_dir().stringify_err()?;
|
||||
let dns_path = app_dir.join(constants::files::DNS_CONFIG);
|
||||
@@ -232,6 +243,7 @@ pub async fn validate_dns_config() -> CmdResult<ValidationOutcome> {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn get_clash_logs() -> CmdResult<Vec<std::string::String>> {
|
||||
let logs = CoreManager::global().get_clash_logs().await.unwrap_or_default();
|
||||
Ok(logs)
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::core::lightweight;
|
||||
use super::CmdResult;
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn entry_lightweight_mode() -> CmdResult {
|
||||
lightweight::entry_lightweight_mode().await;
|
||||
Ok(())
|
||||
|
||||
@@ -5,11 +5,13 @@ use crate::{
|
||||
};
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn probe_listener(request: ListenerProbe) -> CmdResult<ListenerProbeOutcome> {
|
||||
feat::probe_listener(request).await.stringify_err()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn save_proxy_ports(settings: ProxyPortSettings) -> CmdResult<SaveProxyPortsOutcome> {
|
||||
feat::save_proxy_ports(settings)
|
||||
.await
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::time::Duration;
|
||||
use tauri::{command, ipc::Channel};
|
||||
|
||||
#[command]
|
||||
#[specta::specta]
|
||||
pub fn get_unlock_items() -> Vec<UnlockItem> {
|
||||
clash_verge_media_unlock::default_unlock_items()
|
||||
}
|
||||
@@ -26,6 +27,7 @@ async fn create_client() -> Result<Client, String> {
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[specta::specta]
|
||||
pub async fn check_media_unlock(on_complete: Channel<UnlockItem>) -> Result<Vec<UnlockItem>, String> {
|
||||
let client = create_client().await?;
|
||||
Ok(clash_verge_media_unlock::check_media_unlock(&client, |item| {
|
||||
@@ -35,6 +37,7 @@ pub async fn check_media_unlock(on_complete: Channel<UnlockItem>) -> Result<Vec<
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[specta::specta]
|
||||
pub async fn check_media_unlock_item(name: String) -> Result<UnlockItem, String> {
|
||||
let client = create_client().await?;
|
||||
clash_verge_media_unlock::check_media_unlock_item(&client, &name).await
|
||||
|
||||
@@ -4,7 +4,7 @@ use smartstring::alias::String;
|
||||
pub type CmdResult<T = ()> = Result<T, CommandFailure>;
|
||||
|
||||
/// Structured command failure returned to the frontend.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, specta::Type)]
|
||||
pub struct CommandFailure {
|
||||
/// Stable frontend identifier, when classified.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
||||
@@ -3,13 +3,12 @@ use crate::cmd::StringifyErr as _;
|
||||
use crate::core::{proxy_control, sysopt::Sysopt};
|
||||
|
||||
use gethostname::gethostname;
|
||||
use network_interface::NetworkInterface;
|
||||
use serde_yaml_ng::Mapping;
|
||||
use sysproxy::{Autoproxy, Sysproxy};
|
||||
use tauri_plugin_clash_verge_sysinfo;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_sys_proxy() -> CmdResult<Mapping> {
|
||||
#[specta::specta]
|
||||
pub async fn get_sys_proxy() -> CmdResult<SystemProxy> {
|
||||
Sysopt::global().wait_idle().await;
|
||||
// With no network service there is no proxy configured anywhere, which reads as disabled.
|
||||
let sys_proxy = match Sysproxy::get_system_proxy() {
|
||||
@@ -17,40 +16,40 @@ pub async fn get_sys_proxy() -> CmdResult<Mapping> {
|
||||
other => other.stringify_err()?,
|
||||
};
|
||||
let Sysproxy {
|
||||
ref host,
|
||||
ref bypass,
|
||||
ref port,
|
||||
ref enable,
|
||||
host,
|
||||
bypass,
|
||||
port,
|
||||
enable,
|
||||
} = sys_proxy;
|
||||
|
||||
let mut map = Mapping::new();
|
||||
map.insert("enable".into(), (*enable).into());
|
||||
map.insert("server".into(), format!("{}:{}", host, port).into());
|
||||
map.insert("bypass".into(), bypass.as_str().into());
|
||||
Ok(map)
|
||||
Ok(SystemProxy {
|
||||
enable,
|
||||
server: format!("{host}:{port}"),
|
||||
bypass,
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_auto_proxy() -> CmdResult<Mapping> {
|
||||
#[specta::specta]
|
||||
pub async fn get_auto_proxy() -> CmdResult<AutoProxy> {
|
||||
Sysopt::global().wait_idle().await;
|
||||
let auto_proxy = match Autoproxy::get_auto_proxy() {
|
||||
Err(error) if proxy_control::is_missing_network_service(&error) => Autoproxy::default(),
|
||||
other => other.stringify_err()?,
|
||||
};
|
||||
let Autoproxy { ref enable, ref url } = auto_proxy;
|
||||
let Autoproxy { enable, url } = auto_proxy;
|
||||
|
||||
let mut map = Mapping::new();
|
||||
map.insert("enable".into(), (*enable).into());
|
||||
map.insert("url".into(), url.as_str().into());
|
||||
Ok(map)
|
||||
Ok(AutoProxy { enable, url })
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn get_embedded_server_port() -> CmdResult<u16> {
|
||||
crate::utils::server::embedded_server_port().stringify_err()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn get_system_hostname() -> String {
|
||||
match gethostname().into_string() {
|
||||
Ok(name) => name,
|
||||
@@ -62,12 +61,14 @@ pub fn get_system_hostname() -> String {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn get_network_interfaces() -> Vec<String> {
|
||||
tauri_plugin_clash_verge_sysinfo::list_network_interfaces()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_network_interfaces_info() -> CmdResult<Vec<NetworkInterface>> {
|
||||
#[specta::specta]
|
||||
pub fn get_network_interfaces_info() -> CmdResult<Vec<crate::ipc::NetworkInterfaceView>> {
|
||||
use network_interface::{NetworkInterface, NetworkInterfaceConfig as _};
|
||||
|
||||
let names = get_network_interfaces();
|
||||
@@ -77,9 +78,22 @@ pub fn get_network_interfaces_info() -> CmdResult<Vec<NetworkInterface>> {
|
||||
|
||||
for interface in interfaces {
|
||||
if names.contains(&interface.name) {
|
||||
result.push(interface);
|
||||
result.push(interface.into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, specta::Type)]
|
||||
pub struct SystemProxy {
|
||||
pub enable: bool,
|
||||
pub server: String,
|
||||
pub bypass: String,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, specta::Type)]
|
||||
pub struct AutoProxy {
|
||||
pub enable: bool,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
+114
-17
@@ -33,6 +33,7 @@ fn profile_import_error(err: &anyhow::Error) -> std::string::String {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn get_profiles() -> CmdResult<SharedDraft<IProfiles>> {
|
||||
let draft = Config::profiles().await;
|
||||
let data = draft.data_arc();
|
||||
@@ -40,6 +41,7 @@ pub async fn get_profiles() -> CmdResult<SharedDraft<IProfiles>> {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn enhance_profiles() -> CmdResult<ValidationOutcome> {
|
||||
match feat::enhance_profiles().await {
|
||||
Ok(outcome) if outcome.is_valid() => {
|
||||
@@ -65,6 +67,7 @@ pub async fn enhance_profiles() -> CmdResult<ValidationOutcome> {
|
||||
|
||||
#[tauri::command]
|
||||
#[tracing::instrument(skip_all, level = "info", fields(url = %help::mask_url(&url), uid = tracing::field::Empty))]
|
||||
#[specta::specta]
|
||||
pub async fn import_profile(url: std::string::String, option: Option<PrfOption>) -> CmdResult {
|
||||
let item = &mut match PrfItem::from_url(&url, None, None, option.as_ref()).await {
|
||||
Ok(it) => it,
|
||||
@@ -94,7 +97,10 @@ pub async fn import_profile(url: std::string::String, option: Option<PrfOption>)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn reorder_profile(active_id: String, over_id: String) -> CmdResult {
|
||||
#[specta::specta]
|
||||
pub async fn reorder_profile(active_id: std::string::String, over_id: std::string::String) -> CmdResult {
|
||||
let active_id: String = active_id.into();
|
||||
let over_id: String = over_id.into();
|
||||
match profiles_reorder_safe(&active_id, &over_id).await {
|
||||
Ok(_) => {
|
||||
logging!(info, Type::Cmd, "重新排序配置文件: {} -> {}", active_id, over_id);
|
||||
@@ -114,16 +120,16 @@ pub async fn reorder_profile(active_id: String, over_id: String) -> CmdResult {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResult {
|
||||
#[specta::specta]
|
||||
pub async fn create_profile(item: ProfileCreate, file_data: Option<std::string::String>) -> CmdResult {
|
||||
let item: PrfItem = item.into();
|
||||
let file_data = file_data.map(String::from);
|
||||
match profiles_append_item_with_filedata_safe(&item, file_data).await {
|
||||
Ok(_) => {
|
||||
profiles_save_file_safe()
|
||||
.await
|
||||
.with_error_code("PROFILE_CREATE_FAILED")?;
|
||||
logging_error!(Type::Timer, Timer::global().refresh().await);
|
||||
if let Some(uid) = &item.uid {
|
||||
handle::Handle::notify_profile_changed(uid);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => Err(coded_error("PROFILE_CREATE_FAILED", err)),
|
||||
@@ -131,7 +137,9 @@ pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResu
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn update_profile(index: String, option: Option<PrfOption>) -> CmdResult {
|
||||
#[specta::specta]
|
||||
pub async fn update_profile(index: std::string::String, option: Option<PrfOption>) -> CmdResult {
|
||||
let index: String = index.into();
|
||||
match feat::update_profile(&index, option.as_ref(), true).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => {
|
||||
@@ -142,7 +150,9 @@ pub async fn update_profile(index: String, option: Option<PrfOption>) -> CmdResu
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn delete_profile(index: String) -> CmdResult {
|
||||
#[specta::specta]
|
||||
pub async fn delete_profile(index: std::string::String) -> CmdResult {
|
||||
let index: String = index.into();
|
||||
let profile_write_guard = PROFILE_WRITE_LOCK.lock().await;
|
||||
|
||||
let profiles = Config::profiles().await;
|
||||
@@ -313,9 +323,85 @@ async fn perform_config_update(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, serde::Deserialize, specta::Type)]
|
||||
#[serde(default)]
|
||||
pub struct ProfileSelection {
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub current: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, serde::Deserialize, specta::Type)]
|
||||
#[serde(default)]
|
||||
pub struct ProfileCreate {
|
||||
#[serde(rename = "type")]
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub itype: Option<String>,
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub name: Option<String>,
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub desc: Option<String>,
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub url: Option<String>,
|
||||
pub option: Option<PrfOption>,
|
||||
}
|
||||
|
||||
impl From<ProfileCreate> for PrfItem {
|
||||
fn from(item: ProfileCreate) -> Self {
|
||||
Self {
|
||||
itype: item.itype,
|
||||
name: item.name,
|
||||
desc: item.desc,
|
||||
url: item.url,
|
||||
option: item.option,
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, serde::Deserialize, specta::Type)]
|
||||
#[serde(default)]
|
||||
pub struct ProfilePatch {
|
||||
#[serde(rename = "type")]
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub itype: Option<String>,
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub name: Option<String>,
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub desc: Option<String>,
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub file: Option<String>,
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub url: Option<String>,
|
||||
pub selected: Option<Vec<crate::config::PrfSelected>>,
|
||||
#[specta(type = Option<std::collections::BTreeMap<std::string::String, std::string::String>>)]
|
||||
pub latency_test_urls: Option<std::collections::BTreeMap<String, String>>,
|
||||
pub extra: Option<crate::config::PrfExtra>,
|
||||
pub updated: Option<usize>,
|
||||
pub option: Option<PrfOption>,
|
||||
}
|
||||
|
||||
impl From<ProfilePatch> for PrfItem {
|
||||
fn from(patch: ProfilePatch) -> Self {
|
||||
Self {
|
||||
itype: patch.itype,
|
||||
name: patch.name,
|
||||
desc: patch.desc,
|
||||
file: patch.file,
|
||||
url: patch.url,
|
||||
selected: patch.selected,
|
||||
latency_test_urls: patch.latency_test_urls,
|
||||
extra: patch.extra,
|
||||
updated: patch.updated,
|
||||
option: patch.option,
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[tracing::instrument(skip_all, level = "info", fields(target = ?profiles.current))]
|
||||
pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<ValidationOutcome> {
|
||||
#[specta::specta]
|
||||
pub async fn patch_profiles_config(profiles: ProfileSelection) -> CmdResult<ValidationOutcome> {
|
||||
if CURRENT_SWITCHING_PROFILE
|
||||
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
|
||||
.is_err()
|
||||
@@ -325,11 +411,17 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<ValidationO
|
||||
}
|
||||
let _profile_write_guard = PROFILE_WRITE_LOCK.lock().await;
|
||||
|
||||
let profiles = IProfiles {
|
||||
current: profiles.current,
|
||||
items: None,
|
||||
};
|
||||
let target_profile = profiles.current.as_ref();
|
||||
|
||||
let previous_profile = Config::profiles().await.data_arc().current.clone();
|
||||
|
||||
Config::profiles().await.edit_draft(|d| d.patch_config(&profiles));
|
||||
Config::profiles().await.edit_draft(|d| {
|
||||
d.patch_config(&profiles);
|
||||
});
|
||||
|
||||
perform_config_update(target_profile, previous_profile.as_ref())
|
||||
.await
|
||||
@@ -337,15 +429,17 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<ValidationO
|
||||
}
|
||||
|
||||
pub async fn patch_profiles_config_by_profile_index(profile_index: String) -> CmdResult<ValidationOutcome> {
|
||||
let profiles = IProfiles {
|
||||
let profiles = ProfileSelection {
|
||||
current: Some(profile_index),
|
||||
items: None,
|
||||
};
|
||||
patch_profiles_config(profiles).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn patch_profile(index: String, profile: PrfItem) -> CmdResult {
|
||||
#[specta::specta]
|
||||
pub async fn patch_profile(index: std::string::String, profile: ProfilePatch) -> CmdResult {
|
||||
let profile: PrfItem = profile.into();
|
||||
let index: String = index.into();
|
||||
let profiles = Config::profiles().await;
|
||||
let should_refresh_timer = if let Ok(old_profile) = profiles.latest_arc().get_item(&index)
|
||||
&& let Some(new_option) = profile.option.as_ref()
|
||||
@@ -385,7 +479,8 @@ pub async fn patch_profile(index: String, profile: PrfItem) -> CmdResult {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn view_profile(index: String) -> CmdResult {
|
||||
#[specta::specta]
|
||||
pub async fn view_profile(index: std::string::String) -> CmdResult {
|
||||
let profiles = Config::profiles().await;
|
||||
let profiles_ref = profiles.latest_arc();
|
||||
let file = profiles_ref
|
||||
@@ -409,7 +504,8 @@ pub async fn view_profile(index: String) -> CmdResult {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn read_profile_file(index: String) -> CmdResult<String> {
|
||||
#[specta::specta]
|
||||
pub async fn read_profile_file(index: std::string::String) -> CmdResult<std::string::String> {
|
||||
let item = {
|
||||
let profiles = Config::profiles().await;
|
||||
let profiles_ref = profiles.latest_arc();
|
||||
@@ -429,7 +525,7 @@ pub async fn read_profile_file(index: String) -> CmdResult<String> {
|
||||
.join(file.as_str());
|
||||
match tokio::fs::try_exists(&path).await {
|
||||
Ok(true) => {}
|
||||
Ok(false) => return Ok(String::new()),
|
||||
Ok(false) => return Ok(std::string::String::new()),
|
||||
Err(err) => {
|
||||
return Err(coded_error(
|
||||
"PROFILE_READ_FAILED",
|
||||
@@ -440,11 +536,12 @@ pub async fn read_profile_file(index: String) -> CmdResult<String> {
|
||||
}
|
||||
|
||||
let data = item.read_file().await.with_error_code("PROFILE_READ_FAILED")?;
|
||||
Ok(data)
|
||||
Ok(data.into())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_next_update_time(uid: String) -> CmdResult<Option<i64>> {
|
||||
#[specta::specta]
|
||||
pub async fn get_next_update_time(uid: std::string::String) -> CmdResult<Option<i64>> {
|
||||
let timer = Timer::global();
|
||||
let next_time = timer.get_next_update_time(&uid).await;
|
||||
Ok(next_time)
|
||||
|
||||
@@ -18,6 +18,7 @@ use std::{
|
||||
|
||||
/// Merges one selection pair into fresh backend state to avoid stale-list overwrites.
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn record_selected_node(group_name: String, node: String) -> CmdResult<()> {
|
||||
crate::config::profiles::record_selected_node(&group_name, &node)
|
||||
.await
|
||||
@@ -25,6 +26,7 @@ pub async fn record_selected_node(group_name: String, node: String) -> CmdResult
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn forget_selected_node(group_name: String) -> CmdResult<()> {
|
||||
crate::config::profiles::forget_selected_node(&group_name)
|
||||
.await
|
||||
@@ -104,6 +106,7 @@ fn runtime_group_scopes(config: Option<&Mapping>) -> BTreeMap<String, GroupScope
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn get_proxy_view() -> CmdResult<ProxyViewV1> {
|
||||
proxy_view(None).await
|
||||
}
|
||||
@@ -135,6 +138,7 @@ pub(crate) async fn proxy_view(provider_timeout: Option<std::time::Duration>) ->
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn sync_tray_proxy_selection() -> CmdResult<()> {
|
||||
if TRAY_SYNC_RUNNING
|
||||
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
|
||||
|
||||
@@ -1,44 +1,57 @@
|
||||
use super::CmdResult;
|
||||
use crate::{cmd::StringifyErr as _, config::Config, core::CoreManager, utils::yaml_emitter};
|
||||
use anyhow::{Context as _, anyhow};
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use serde_yaml_ng::Mapping;
|
||||
use smartstring::alias::String;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_runtime_config() -> CmdResult<Option<Mapping>> {
|
||||
Ok(Config::runtime().await.latest_arc().config.clone())
|
||||
#[specta::specta]
|
||||
pub async fn get_runtime_config() -> CmdResult<Option<crate::ipc::YamlMapping>> {
|
||||
Ok(Config::runtime()
|
||||
.await
|
||||
.latest_arc()
|
||||
.config
|
||||
.clone()
|
||||
.map(crate::ipc::YamlMapping))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_runtime_yaml() -> CmdResult<String> {
|
||||
#[specta::specta]
|
||||
pub async fn get_runtime_yaml() -> CmdResult<std::string::String> {
|
||||
let runtime = Config::runtime().await;
|
||||
let runtime = runtime.latest_arc();
|
||||
|
||||
let config = runtime.config.as_ref();
|
||||
config
|
||||
.ok_or_else(|| anyhow!("failed to parse config to yaml file"))
|
||||
.and_then(|config| {
|
||||
yaml_emitter::to_mihomo_config_string(config)
|
||||
.context("failed to convert config to yaml")
|
||||
.map(|s| s.into())
|
||||
})
|
||||
.and_then(|config| yaml_emitter::to_mihomo_config_string(config).context("failed to convert config to yaml"))
|
||||
.stringify_err()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_runtime_logs() -> CmdResult<HashMap<String, Vec<(String, String)>>> {
|
||||
Ok(Config::runtime().await.latest_arc().chain_logs.clone())
|
||||
#[specta::specta]
|
||||
pub async fn get_runtime_logs() -> CmdResult<RuntimeLogs> {
|
||||
Ok(RuntimeLogs(Config::runtime().await.latest_arc().chain_logs.clone()))
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, specta::Type)]
|
||||
#[serde(transparent)]
|
||||
pub struct RuntimeLogs(
|
||||
#[specta(type = HashMap<std::string::String, Vec<(std::string::String, std::string::String)>>)]
|
||||
HashMap<String, Vec<(String, String)>>,
|
||||
);
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn take_discarded_keys_notice() -> Option<std::string::String> {
|
||||
crate::enhance::take_discarded_keys_notice().map(Into::into)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn take_discarded_keys_notice() -> Option<String> {
|
||||
crate::enhance::take_discarded_keys_notice()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_runtime_proxy_chain_config(proxy_chain_exit_node: String) -> CmdResult<String> {
|
||||
#[specta::specta]
|
||||
pub async fn get_runtime_proxy_chain_config(
|
||||
proxy_chain_exit_node: std::string::String,
|
||||
) -> CmdResult<std::string::String> {
|
||||
let runtime = Config::runtime().await;
|
||||
let runtime = runtime.latest_arc();
|
||||
|
||||
@@ -59,7 +72,7 @@ pub async fn get_runtime_proxy_chain_config(proxy_chain_exit_node: String) -> Cm
|
||||
false
|
||||
}
|
||||
}) {
|
||||
proxies_chain.push(proxy.to_owned());
|
||||
proxies_chain.push(proxy);
|
||||
proxy_name = proxy.get("dialer-proxy").map(|x| x.as_str());
|
||||
}
|
||||
|
||||
@@ -68,18 +81,17 @@ pub async fn get_runtime_proxy_chain_config(proxy_chain_exit_node: String) -> Cm
|
||||
.find(|proxy| proxy.get("name").map(|x| x.as_str()) == proxy_name)
|
||||
&& !proxies_chain.is_empty()
|
||||
{
|
||||
proxies_chain.push(entry_proxy.to_owned());
|
||||
proxies_chain.push(entry_proxy);
|
||||
}
|
||||
|
||||
proxies_chain.reverse();
|
||||
|
||||
let mut config: HashMap<String, Vec<serde_yaml_ng::Value>> = HashMap::new();
|
||||
let mut config: HashMap<String, Vec<&serde_yaml_ng::Value>> = HashMap::new();
|
||||
|
||||
config.insert("proxies".into(), proxies_chain);
|
||||
|
||||
yaml_emitter::to_mihomo_config_string(&config)
|
||||
.context("YAML generation failed")
|
||||
.map(|s| s.into())
|
||||
.stringify_err()
|
||||
} else {
|
||||
Err("failed to get proxies or proxy-groups".into())
|
||||
@@ -87,20 +99,13 @@ pub async fn get_runtime_proxy_chain_config(proxy_chain_exit_node: String) -> Cm
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn update_proxy_chain_config_in_runtime(proxy_chain_config: Option<serde_yaml_ng::Value>) -> CmdResult<()> {
|
||||
match CoreManager::global()
|
||||
#[specta::specta]
|
||||
pub async fn update_proxy_chain_config_in_runtime(
|
||||
proxy_chain_config: Option<crate::ipc::YamlValue>,
|
||||
) -> CmdResult<crate::core::validate::ValidationOutcome> {
|
||||
let proxy_chain_config = proxy_chain_config.map(|value| value.0);
|
||||
CoreManager::global()
|
||||
.update_runtime_config(|d| d.update_proxy_chain_config(proxy_chain_config))
|
||||
.await
|
||||
{
|
||||
Ok(outcome) if outcome.is_valid() => {}
|
||||
Ok(outcome) => logging!(
|
||||
warn,
|
||||
Type::Core,
|
||||
"Failed to apply runtime proxy chain config: {}",
|
||||
outcome
|
||||
),
|
||||
Err(err) => logging!(error, Type::Core, "Failed to apply runtime proxy chain config: {err:#}"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
.stringify_err()
|
||||
}
|
||||
|
||||
@@ -16,7 +16,13 @@ use tokio::fs;
|
||||
|
||||
/// 保存profiles的配置
|
||||
#[tauri::command]
|
||||
pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdResult<ValidationOutcome> {
|
||||
#[specta::specta]
|
||||
pub async fn save_profile_file(
|
||||
index: std::string::String,
|
||||
file_data: Option<std::string::String>,
|
||||
) -> CmdResult<ValidationOutcome> {
|
||||
let index: String = index.into();
|
||||
let file_data = file_data.map(String::from);
|
||||
let file_data = match file_data {
|
||||
Some(d) => d,
|
||||
None => return Ok(ValidationOutcome::Valid),
|
||||
|
||||
@@ -8,11 +8,11 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, serde::Serialize)]
|
||||
#[derive(Debug, PartialEq, Eq, serde::Serialize, specta::Type)]
|
||||
#[serde(tag = "status", rename_all = "camelCase")]
|
||||
pub enum ServiceInstallOutcome {
|
||||
Installed,
|
||||
#[cfg(any(windows, test))]
|
||||
#[cfg_attr(not(any(windows, test)), allow(dead_code))]
|
||||
Sidecar {
|
||||
reason: String,
|
||||
},
|
||||
@@ -82,11 +82,13 @@ where
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn install_service() -> CmdResult<ServiceInstallOutcome> {
|
||||
execute_service_operation_sync(ServiceStatus::InstallRequired, "SERVICE_INSTALL_FAILED").await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn uninstall_service() -> CmdResult {
|
||||
CoreManager::global()
|
||||
.uninstall_service_and_start_sidecar()
|
||||
@@ -95,16 +97,19 @@ pub async fn uninstall_service() -> CmdResult {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn reinstall_service() -> CmdResult<ServiceInstallOutcome> {
|
||||
execute_service_operation_sync(ServiceStatus::ReinstallRequired, "SERVICE_REINSTALL_FAILED").await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn repair_service() -> CmdResult<ServiceInstallOutcome> {
|
||||
execute_service_operation_sync(ServiceStatus::ForceReinstallRequired, "SERVICE_REPAIR_FAILED").await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn continue_with_sidecar() -> CmdResult {
|
||||
crate::core::CoreManager::global()
|
||||
.continue_with_sidecar(None)
|
||||
@@ -113,31 +118,37 @@ pub async fn continue_with_sidecar() -> CmdResult {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn get_sidecar_failure() -> crate::core::notification::SidecarFailureSnapshot {
|
||||
crate::core::notification::sidecar_failure_snapshot()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn take_service_fallback_notice() -> Option<crate::core::service::ServiceFallbackNotice> {
|
||||
crate::core::service::take_service_fallback_notice()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn get_core_startup_error() -> Option<crate::core::manager::CoreFailure> {
|
||||
crate::core::CoreManager::global().get_startup_error()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn take_service_repair_notice() -> bool {
|
||||
crate::core::service::take_service_repair_notice()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn take_service_owner_notice() -> Option<String> {
|
||||
crate::core::service::take_service_owner_notice()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn sync_runtime_providers() {
|
||||
if matches!(*CoreManager::global().get_running_mode(), RunningMode::Service) {
|
||||
request_runtime_provider_sync(timing::RUNTIME_PROVIDER_SETTLE);
|
||||
|
||||
@@ -5,11 +5,13 @@ use crate::core::{
|
||||
|
||||
/// Returns one coherent core/service snapshot instead of independently refreshed state.
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn get_runtime_state() -> Result<RunStateView, String> {
|
||||
Ok(RUN_STATE.settled().await.to_view())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn get_pending_failures() -> Vec<PendingFailure> {
|
||||
notification::pending_failures()
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ mod platform {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn invoke_uwp_tool() -> CmdResult {
|
||||
platform::invoke_uwp_tool()
|
||||
}
|
||||
|
||||
@@ -9,12 +9,14 @@ use clash_verge_draft::SharedDraft;
|
||||
|
||||
/// 获取Verge配置
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn get_verge_config() -> CmdResult<SharedDraft<IVerge>> {
|
||||
feat::fetch_verge_config().await.stringify_err()
|
||||
}
|
||||
|
||||
/// 修改Verge配置
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn patch_verge_config(payload: IVerge) -> CmdResult {
|
||||
let operation = system_proxy_operation(&payload);
|
||||
let result = match operation {
|
||||
|
||||
@@ -4,11 +4,18 @@ use crate::{
|
||||
config::{Config, IVerge},
|
||||
core, feat,
|
||||
};
|
||||
use reqwest_dav::list_cmd::ListFile;
|
||||
use smartstring::alias::String;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn save_webdav_config(url: String, username: String, password: String) -> CmdResult<()> {
|
||||
#[specta::specta]
|
||||
pub async fn save_webdav_config(
|
||||
url: std::string::String,
|
||||
username: std::string::String,
|
||||
password: std::string::String,
|
||||
) -> CmdResult<()> {
|
||||
let url: String = url.into();
|
||||
let username: String = username.into();
|
||||
let password: String = password.into();
|
||||
let patch = IVerge {
|
||||
webdav_url: Some(url),
|
||||
webdav_username: Some(username),
|
||||
@@ -25,22 +32,31 @@ pub async fn save_webdav_config(url: String, username: String, password: String)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn create_webdav_backup() -> CmdResult<()> {
|
||||
feat::create_backup_and_upload_webdav().await.stringify_err()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn list_webdav_backup() -> CmdResult<Vec<ListFile>> {
|
||||
feat::list_wevdav_backup().await.stringify_err()
|
||||
#[specta::specta]
|
||||
pub async fn list_webdav_backup() -> CmdResult<Vec<crate::ipc::WebDavFile>> {
|
||||
feat::list_wevdav_backup()
|
||||
.await
|
||||
.map(|files| files.into_iter().map(Into::into).collect())
|
||||
.stringify_err()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn delete_webdav_backup(filename: String) -> CmdResult<()> {
|
||||
#[specta::specta]
|
||||
pub async fn delete_webdav_backup(filename: std::string::String) -> CmdResult<()> {
|
||||
let filename: String = filename.into();
|
||||
feat::delete_webdav_backup(filename).await.stringify_err()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn restore_webdav_backup(filename: String) -> CmdResult<()> {
|
||||
#[specta::specta]
|
||||
pub async fn restore_webdav_backup(filename: std::string::String) -> CmdResult<()> {
|
||||
let filename: String = filename.into();
|
||||
feat::restore_webdav_backup(filename)
|
||||
.await
|
||||
.map_err(|error| super::proxy_aware_error(&error))
|
||||
|
||||
@@ -308,7 +308,7 @@ impl IClashTemp {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq, specta::Type)]
|
||||
pub struct ClashInfo {
|
||||
/// clash core port
|
||||
pub mixed_port: u16,
|
||||
@@ -364,14 +364,14 @@ fn test_clash_info() {
|
||||
assert_eq!(get_case(8888, "192.168.1.1:80800"), get_result(8888, "127.0.0.1:9097"));
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq, specta::Type)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct IClashExternalControllerCors {
|
||||
pub allow_origins: Option<Vec<String>>,
|
||||
pub allow_private_network: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq, specta::Type)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct IClash {
|
||||
pub mixed_port: Option<u16>,
|
||||
@@ -387,7 +387,7 @@ pub struct IClash {
|
||||
pub external_controller_cors: Option<IClashExternalControllerCors>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq, specta::Type)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct IClashTUN {
|
||||
pub enable: Option<bool>,
|
||||
@@ -397,7 +397,7 @@ pub struct IClashTUN {
|
||||
pub dns_hijack: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq, specta::Type)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct IClashDNS {
|
||||
pub enable: Option<bool>,
|
||||
@@ -414,7 +414,7 @@ pub struct IClashDNS {
|
||||
pub nameserver_policy: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq, specta::Type)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct IClashFallbackFilter {
|
||||
pub geoip: Option<bool>,
|
||||
|
||||
@@ -106,10 +106,11 @@ impl DnsOverrideState {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize, specta::Type)]
|
||||
pub struct ProfileDnsSettings {
|
||||
pub enabled: bool,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub confirmation: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
@@ -27,28 +27,34 @@ pub(super) fn normalize_profile_home_url(raw: &str) -> Option<String> {
|
||||
Some(url.to_string().into())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Default, specta::Type)]
|
||||
#[serde(default)]
|
||||
pub struct PrfItem {
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub uid: Option<String>,
|
||||
|
||||
/// enum value: remote | local | script | merge
|
||||
#[serde(rename = "type")]
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub itype: Option<String>,
|
||||
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub name: Option<String>,
|
||||
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub file: Option<String>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub desc: Option<String>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub url: Option<String>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub selected: Option<Vec<PrfSelected>>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[specta(type = Option<std::collections::BTreeMap<std::string::String, std::string::String>>)]
|
||||
pub latency_test_urls: Option<std::collections::BTreeMap<String, String>>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -60,19 +66,24 @@ pub struct PrfItem {
|
||||
pub option: Option<PrfOption>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub home: Option<String>,
|
||||
|
||||
#[serde(skip)]
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub file_data: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq, specta::Type)]
|
||||
#[serde(default)]
|
||||
pub struct PrfSelected {
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub name: Option<String>,
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub now: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, Deserialize, Serialize)]
|
||||
#[derive(Default, Debug, Clone, Copy, Deserialize, Serialize, specta::Type)]
|
||||
pub struct PrfExtra {
|
||||
pub upload: u64,
|
||||
pub download: u64,
|
||||
@@ -80,9 +91,11 @@ pub struct PrfExtra {
|
||||
pub expire: u64,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq, specta::Type)]
|
||||
#[serde(default)]
|
||||
pub struct PrfOption {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub user_agent: Option<String>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -105,15 +118,15 @@ pub struct PrfOption {
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub allow_auto_update: Option<bool>,
|
||||
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub merge: Option<String>,
|
||||
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub script: Option<String>,
|
||||
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub rules: Option<String>,
|
||||
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub proxies: Option<String>,
|
||||
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub groups: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
@@ -48,8 +48,10 @@ const SELECTED_NODES_SETTLE_INTERVAL: Duration = Duration::from_secs(1);
|
||||
const SELECTED_NODES_FIRST_PASS_BUDGET: Duration = Duration::from_secs(3);
|
||||
|
||||
/// Define the `profiles.yaml` schema
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, specta::Type)]
|
||||
#[serde(default)]
|
||||
pub struct IProfiles {
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub current: Option<String>,
|
||||
|
||||
pub items: Option<Vec<PrfItem>>,
|
||||
|
||||
@@ -11,26 +11,30 @@ use serde::{Deserialize, Serialize};
|
||||
use smartstring::alias::String;
|
||||
|
||||
/// ### `verge.yaml` schema
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, specta::Type)]
|
||||
#[serde(default)]
|
||||
pub struct IVerge {
|
||||
/// silent | error | warn | info | debug | trace
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub app_log_level: Option<String>,
|
||||
|
||||
/// Maximum app log size in KiB.
|
||||
pub app_log_max_size: Option<u64>,
|
||||
|
||||
pub app_log_max_count: Option<usize>,
|
||||
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub language: Option<String>,
|
||||
|
||||
/// `light` or `dark` or `system`
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub theme_mode: Option<String>,
|
||||
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub tray_event: Option<String>,
|
||||
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub env_type: Option<String>,
|
||||
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub start_page: Option<String>,
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub startup_script: Option<String>,
|
||||
|
||||
pub traffic_graph: Option<bool>,
|
||||
@@ -48,18 +52,23 @@ pub struct IVerge {
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub tray_icon: Option<String>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub menu_icon: Option<String>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[specta(type = Option<Vec<std::string::String>>)]
|
||||
pub menu_order: Option<Vec<String>>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub proxy_group_tools_position: Option<String>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub notice_position: Option<String>,
|
||||
|
||||
pub collapse_navbar: Option<bool>,
|
||||
@@ -84,39 +93,43 @@ pub struct IVerge {
|
||||
pub enable_dns_settings: Option<bool>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
|
||||
#[specta(type = std::collections::BTreeMap<std::string::String, super::dns::ProfileDnsSettings>)]
|
||||
pub profile_dns_settings: std::collections::BTreeMap<String, super::dns::ProfileDnsSettings>,
|
||||
|
||||
pub use_default_bypass: Option<bool>,
|
||||
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub system_proxy_bypass: Option<String>,
|
||||
|
||||
pub proxy_guard_duration: Option<u64>,
|
||||
|
||||
pub proxy_auto_config: Option<bool>,
|
||||
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub pac_file_content: Option<String>,
|
||||
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub proxy_host: Option<String>,
|
||||
|
||||
pub theme_setting: Option<IVergeTheme>,
|
||||
|
||||
#[specta(type = Option<Vec<std::string::String>>)]
|
||||
pub web_ui_list: Option<Vec<String>>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub clash_core: Option<String>,
|
||||
|
||||
/// format: {func},{key}
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[specta(type = Option<Vec<std::string::String>>)]
|
||||
pub hotkeys: Option<Vec<String>>,
|
||||
|
||||
pub enable_global_hotkey: Option<bool>,
|
||||
|
||||
#[specta(type = Option<crate::ipc::JsonValue>)]
|
||||
pub home_cards: Option<serde_json::Value>,
|
||||
|
||||
pub auto_close_connection: Option<bool>,
|
||||
|
||||
pub auto_check_update: Option<bool>,
|
||||
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub default_latency_test: Option<String>,
|
||||
|
||||
pub default_latency_timeout: Option<i16>,
|
||||
@@ -171,6 +184,7 @@ pub struct IVerge {
|
||||
skip_serializing_if = "Option::is_none",
|
||||
default
|
||||
)]
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub webdav_url: Option<String>,
|
||||
|
||||
#[serde(
|
||||
@@ -179,6 +193,7 @@ pub struct IVerge {
|
||||
skip_serializing_if = "Option::is_none",
|
||||
default
|
||||
)]
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub webdav_username: Option<String>,
|
||||
|
||||
#[serde(
|
||||
@@ -187,12 +202,14 @@ pub struct IVerge {
|
||||
skip_serializing_if = "Option::is_none",
|
||||
default
|
||||
)]
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub webdav_password: Option<String>,
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub enable_tray_speed: Option<bool>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub tray_proxy_groups_display_mode: Option<String>,
|
||||
pub tray_inline_outbound_modes: Option<bool>,
|
||||
|
||||
@@ -209,27 +226,41 @@ pub struct IVerge {
|
||||
pub enable_external_controller: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, specta::Type)]
|
||||
#[serde(default)]
|
||||
pub struct IVergeTestItem {
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub uid: Option<String>,
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub name: Option<String>,
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub icon: Option<String>,
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, specta::Type)]
|
||||
#[serde(default)]
|
||||
pub struct IVergeTheme {
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub primary_color: Option<String>,
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub secondary_color: Option<String>,
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub primary_text: Option<String>,
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub secondary_text: Option<String>,
|
||||
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub info_color: Option<String>,
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub error_color: Option<String>,
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub warning_color: Option<String>,
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub success_color: Option<String>,
|
||||
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub font_family: Option<String>,
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub css_injection: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ pub(crate) fn proxy_listener_keys() -> impl Iterator<Item = &'static str> {
|
||||
PROXY_LISTENERS.iter().map(|(key, _, _)| *key)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, specta::Type)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProxyPortSettings {
|
||||
pub mixed_port: u16,
|
||||
@@ -48,27 +48,27 @@ pub struct ProxyPortSettings {
|
||||
pub tproxy: ToggleableProxyPort,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize)]
|
||||
#[derive(Clone, Copy, Debug, Deserialize, specta::Type)]
|
||||
pub struct ToggleableProxyPort {
|
||||
pub enabled: bool,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, specta::Type)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ListenerProbe {
|
||||
pub address: String,
|
||||
pub transports: Vec<ListenerTransport>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash, Serialize)]
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash, Serialize, specta::Type)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ListenerTransport {
|
||||
Tcp,
|
||||
Udp,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize)]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, specta::Type)]
|
||||
#[serde(tag = "status", rename_all = "camelCase")]
|
||||
pub enum ListenerProbeOutcome {
|
||||
Available,
|
||||
@@ -77,7 +77,7 @@ pub enum ListenerProbeOutcome {
|
||||
Indeterminate { message: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize)]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, specta::Type)]
|
||||
#[serde(tag = "status", rename_all = "camelCase")]
|
||||
pub enum SaveProxyPortsOutcome {
|
||||
Saved,
|
||||
|
||||
@@ -57,7 +57,7 @@ fn claim_core_readiness_generation(state: &AtomicU64, captured_generation: u64)
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, serde::Serialize, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Debug, serde::Serialize, PartialEq, Eq, specta::Type)]
|
||||
pub enum RunningMode {
|
||||
Service,
|
||||
Sidecar,
|
||||
@@ -75,7 +75,7 @@ impl fmt::Display for RunningMode {
|
||||
}
|
||||
|
||||
/// Why the Core is not running, kept until a Core starts again.
|
||||
#[derive(Clone, Debug, serde::Serialize, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, serde::Serialize, PartialEq, Eq, specta::Type)]
|
||||
#[serde(tag = "kind", content = "detail", rename_all = "camelCase")]
|
||||
pub enum CoreFailure {
|
||||
StartFailed(String),
|
||||
|
||||
@@ -52,10 +52,7 @@ impl Handle {
|
||||
/// Sent on every transition, so the frontend does not have to poll to notice that the Core
|
||||
/// stopped or that the Service came back.
|
||||
pub fn notify_run_state(state: &crate::core::runstate::RunStateView) {
|
||||
let Ok(state) = serde_json::to_value(state) else {
|
||||
return;
|
||||
};
|
||||
Self::send_event(FrontendEvent::RunStateChanged { state });
|
||||
Self::send_event(FrontendEvent::RunStateChanged { state: state.clone() });
|
||||
}
|
||||
|
||||
pub fn notify_profile_changed(profile_id: &String) {
|
||||
|
||||
@@ -3,9 +3,8 @@ pub use announce::{Refresh, announce};
|
||||
pub mod desktop;
|
||||
pub mod handle;
|
||||
pub mod notification;
|
||||
pub use notification::frontend_wire_contract;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, specta::Type)]
|
||||
pub enum NoticeStatus {
|
||||
#[serde(rename = "info")]
|
||||
Info,
|
||||
@@ -83,6 +82,7 @@ impl NoticeStatus {
|
||||
/// Every variant in golden order. The wire-contract export and the golden
|
||||
/// test both derive their variant list from here; expected strings stay
|
||||
/// hand-written in the test so renames cannot slip through.
|
||||
#[cfg(test)]
|
||||
pub const ALL: [Self; 34] = [
|
||||
Self::Info,
|
||||
Self::ImportSubUrlOk,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::NoticeStatus;
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use parking_lot::Mutex;
|
||||
#[cfg(test)]
|
||||
use serde_json::json;
|
||||
use smartstring::alias::String;
|
||||
use std::sync::Arc;
|
||||
@@ -31,7 +32,7 @@ pub enum FrontendEvent<'a> {
|
||||
uid: &'a String,
|
||||
},
|
||||
RunStateChanged {
|
||||
state: serde_json::Value,
|
||||
state: crate::core::runstate::RunStateView,
|
||||
},
|
||||
PendingFailuresChanged,
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -42,7 +43,7 @@ pub enum FrontendEvent<'a> {
|
||||
|
||||
/// Operation associated with a pending failure.
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, specta::Type)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum FailedOperation {
|
||||
SystemProxyEnable,
|
||||
@@ -72,12 +73,14 @@ impl FailedOperation {
|
||||
}
|
||||
|
||||
/// Latest unresolved failure for one stable code.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, specta::Type)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PendingFailure {
|
||||
/// Stable code used as the table key.
|
||||
#[specta(type = std::string::String)]
|
||||
pub code: String,
|
||||
/// Full diagnostic context chain.
|
||||
#[specta(type = std::string::String)]
|
||||
pub detail: String,
|
||||
pub operation: FailedOperation,
|
||||
/// Monotonic identity for repeated failures under the same code.
|
||||
@@ -241,9 +244,10 @@ pub fn what_was_asked() -> FailedOperation {
|
||||
static PENDING_FAILURES: LazyLock<FailureTable> = LazyLock::new(FailureTable::default);
|
||||
|
||||
/// A removal has a revision too, so a delayed read cannot restore a resolved failure.
|
||||
#[derive(Debug, Default, Clone, serde::Serialize)]
|
||||
#[derive(Debug, Default, Clone, serde::Serialize, specta::Type)]
|
||||
pub struct SidecarFailureSnapshot {
|
||||
pub revision: u64,
|
||||
#[specta(type = Option<std::string::String>)]
|
||||
pub detail: Option<String>,
|
||||
}
|
||||
|
||||
@@ -327,24 +331,7 @@ impl NotificationSystem {
|
||||
}
|
||||
|
||||
fn serialize_event(event: FrontendEvent) -> (&'static str, Result<serde_json::Value, serde_json::Error>) {
|
||||
match event {
|
||||
FrontendEvent::RefreshClash => ("verge://refresh-clash-config", Ok(json!("yes"))),
|
||||
FrontendEvent::RefreshVerge => ("verge://refresh-verge-config", Ok(json!("yes"))),
|
||||
FrontendEvent::RefreshProfiles => ("verge://refresh-profiles", Ok(json!("yes"))),
|
||||
FrontendEvent::RefreshProxyConfig => ("verge://refresh-proxy-config", Ok(serde_json::Value::Null)),
|
||||
FrontendEvent::NoticeMessage { status, message } => (
|
||||
"verge://notice-message",
|
||||
serde_json::to_value((status, message.as_ref())),
|
||||
),
|
||||
FrontendEvent::ProfileChanged { current_profile_id } => ("profile-changed", Ok(json!(current_profile_id))),
|
||||
FrontendEvent::TimerUpdated { profile_index } => ("verge://timer-updated", Ok(json!(profile_index))),
|
||||
FrontendEvent::ProfileUpdateStarted { uid } => ("profile-update-started", Ok(json!({ "uid": uid }))),
|
||||
FrontendEvent::ProfileUpdateCompleted { uid } => ("profile-update-completed", Ok(json!({ "uid": uid }))),
|
||||
FrontendEvent::RunStateChanged { state } => ("verge://run-state-changed", Ok(state)),
|
||||
FrontendEvent::PendingFailuresChanged => ("verge://pending-failures-changed", Ok(serde_json::Value::Null)),
|
||||
#[cfg(target_os = "linux")]
|
||||
FrontendEvent::ThemeChanged { theme } => ("tauri://theme-changed", serde_json::to_value(theme)),
|
||||
}
|
||||
serialize_frontend_event(event)
|
||||
}
|
||||
|
||||
pub(crate) fn send_event(app_handle: AppHandle, event: FrontendEvent) {
|
||||
@@ -381,20 +368,21 @@ mod wire_tests {
|
||||
}
|
||||
|
||||
/// One sample per `FrontendEvent` variant, paired with its wire name and payload.
|
||||
/// Shared by the wire golden test and the contract export so the two cannot drift.
|
||||
#[cfg(test)]
|
||||
fn sample_frontend_events(uid: &String) -> Vec<(FrontendEvent<'_>, &'static str, serde_json::Value)> {
|
||||
let state = crate::core::runstate::RunState {
|
||||
health: Default::default(),
|
||||
pending: None,
|
||||
sidecar_allowed: false,
|
||||
mode: crate::core::manager::RunningMode::NotRunning,
|
||||
is_admin: false,
|
||||
op_in_flight: false,
|
||||
}
|
||||
.to_view();
|
||||
vec![
|
||||
(
|
||||
FrontendEvent::RefreshClash,
|
||||
"verge://refresh-clash-config",
|
||||
json!("yes"),
|
||||
),
|
||||
(
|
||||
FrontendEvent::RefreshVerge,
|
||||
"verge://refresh-verge-config",
|
||||
json!("yes"),
|
||||
),
|
||||
(FrontendEvent::RefreshProfiles, "verge://refresh-profiles", json!("yes")),
|
||||
(FrontendEvent::RefreshClash, "verge://refresh-clash-config", json!(null)),
|
||||
(FrontendEvent::RefreshVerge, "verge://refresh-verge-config", json!(null)),
|
||||
(FrontendEvent::RefreshProfiles, "verge://refresh-profiles", json!(null)),
|
||||
(
|
||||
FrontendEvent::RefreshProxyConfig,
|
||||
"verge://refresh-proxy-config",
|
||||
@@ -414,11 +402,9 @@ fn sample_frontend_events(uid: &String) -> Vec<(FrontendEvent<'_>, &'static str,
|
||||
json!("profile"),
|
||||
),
|
||||
(
|
||||
FrontendEvent::RunStateChanged {
|
||||
state: json!({"running": true}),
|
||||
},
|
||||
FrontendEvent::RunStateChanged { state: state.clone() },
|
||||
"verge://run-state-changed",
|
||||
json!({"running": true}),
|
||||
serde_json::to_value(state).unwrap_or(serde_json::Value::Null),
|
||||
),
|
||||
(
|
||||
FrontendEvent::PendingFailuresChanged,
|
||||
@@ -445,24 +431,41 @@ fn sample_frontend_events(uid: &String) -> Vec<(FrontendEvent<'_>, &'static str,
|
||||
]
|
||||
}
|
||||
|
||||
/// Machine-readable form of the frontend wire contract: every event name with a
|
||||
/// serialized sample payload, plus every notice status string. The sample
|
||||
/// payload's JSON shape (string / null / array / object) is what the frontend
|
||||
/// generator maps to TypeScript types. `ThemeChanged` is a Tauri built-in
|
||||
/// linux-only window event, not part of this contract.
|
||||
pub fn frontend_wire_contract() -> serde_json::Value {
|
||||
let uid = String::from("sample");
|
||||
let events: Vec<_> = sample_frontend_events(&uid)
|
||||
.into_iter()
|
||||
.map(|(event, _, _)| {
|
||||
let (name, payload) = NotificationSystem::serialize_event(event);
|
||||
json!({ "name": name, "sample": payload.unwrap_or(serde_json::Value::Null) })
|
||||
})
|
||||
.collect();
|
||||
let statuses: Vec<_> = NoticeStatus::ALL
|
||||
.iter()
|
||||
// A unit variant with a string rename cannot fail to serialize.
|
||||
.filter_map(|status| serde_json::to_value(status).ok())
|
||||
.collect();
|
||||
json!({ "version": 1, "events": events, "noticeStatuses": statuses })
|
||||
macro_rules! frontend_events {
|
||||
($( $field:ident: $payload:ty = $name:literal, $pattern:pat => $value:expr; )*) => {
|
||||
#[derive(serde::Serialize, specta::Type)]
|
||||
pub struct VergeEventPayloads {
|
||||
$(#[serde(rename = $name)] pub $field: $payload,)*
|
||||
}
|
||||
|
||||
fn serialize_frontend_event(event: FrontendEvent) -> (&'static str, Result<serde_json::Value, serde_json::Error>) {
|
||||
match event {
|
||||
$($pattern => {
|
||||
let payload: $payload = $value;
|
||||
($name, serde_json::to_value(payload))
|
||||
},)*
|
||||
#[cfg(target_os = "linux")]
|
||||
FrontendEvent::ThemeChanged { theme } => ("tauri://theme-changed", serde_json::to_value(theme)),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
frontend_events! {
|
||||
refresh_clash: () = "verge://refresh-clash-config", FrontendEvent::RefreshClash => ();
|
||||
refresh_verge: () = "verge://refresh-verge-config", FrontendEvent::RefreshVerge => ();
|
||||
refresh_profiles: () = "verge://refresh-profiles", FrontendEvent::RefreshProfiles => ();
|
||||
refresh_proxy: () = "verge://refresh-proxy-config", FrontendEvent::RefreshProxyConfig => ();
|
||||
notice: (NoticeStatus, std::string::String) = "verge://notice-message", FrontendEvent::NoticeMessage { status, ref message } => (status, message.to_string());
|
||||
profile_changed: std::string::String = "profile-changed", FrontendEvent::ProfileChanged { current_profile_id } => current_profile_id.to_string();
|
||||
timer_updated: std::string::String = "verge://timer-updated", FrontendEvent::TimerUpdated { profile_index } => profile_index.to_string();
|
||||
profile_started: ProfileUpdatePayload = "profile-update-started", FrontendEvent::ProfileUpdateStarted { uid } => ProfileUpdatePayload { uid: uid.to_string() };
|
||||
profile_completed: ProfileUpdatePayload = "profile-update-completed", FrontendEvent::ProfileUpdateCompleted { uid } => ProfileUpdatePayload { uid: uid.to_string() };
|
||||
run_state: crate::core::runstate::RunStateView = "verge://run-state-changed", FrontendEvent::RunStateChanged { state } => state;
|
||||
pending_failures: () = "verge://pending-failures-changed", FrontendEvent::PendingFailuresChanged => ();
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, specta::Type)]
|
||||
pub struct ProfileUpdatePayload {
|
||||
pub uid: std::string::String,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||||
|
||||
use serde::Serialize;
|
||||
use serde::{Serialize, Serializer, ser::SerializeMap as _};
|
||||
use tauri_plugin_mihomo::models::{
|
||||
DelayHistory, Extra, Proxies, Proxy, ProxyProvider, ProxyProviders, ProxyType, VehicleType,
|
||||
};
|
||||
@@ -20,7 +20,7 @@ pub struct GroupScope {
|
||||
|
||||
pub struct ProxyViewBuilder;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize)]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, specta::Type)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProxyViewV1 {
|
||||
pub schema_version: u8,
|
||||
@@ -34,21 +34,21 @@ pub struct ProxyViewV1 {
|
||||
pub providers: Vec<ProxyProviderView>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, specta::Type)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ProxyViewOrderSource {
|
||||
Runtime,
|
||||
Fallback,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, specta::Type)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ProxyViewProviderState {
|
||||
Ready,
|
||||
Unavailable,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize)]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, specta::Type)]
|
||||
pub struct ProxyCapabilities {
|
||||
pub udp: bool,
|
||||
pub xudp: bool,
|
||||
@@ -57,13 +57,35 @@ pub struct ProxyCapabilities {
|
||||
pub smux: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize)]
|
||||
#[derive(Serialize, specta::Type)]
|
||||
struct ProxyLatencyHistory<'a> {
|
||||
#[specta(type = Vec<crate::ipc::DelayHistory>)]
|
||||
history: &'a [DelayHistory],
|
||||
}
|
||||
|
||||
fn serialize_proxy_latency_history<S: Serializer>(
|
||||
extra: &HashMap<String, Extra>,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
let mut map = serializer.serialize_map(Some(extra.len()))?;
|
||||
for (url, extra) in extra {
|
||||
map.serialize_entry(
|
||||
url,
|
||||
&ProxyLatencyHistory {
|
||||
history: &extra.history,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, specta::Type)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProxyGroupView {
|
||||
pub name: String,
|
||||
#[serde(rename = "type")]
|
||||
#[specta(type = String)]
|
||||
pub proxy_type: ProxyType,
|
||||
pub alive: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub now: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -74,22 +96,28 @@ pub struct ProxyGroupView {
|
||||
pub icon: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub test_url: Option<String>,
|
||||
#[specta(type = Vec<crate::ipc::DelayHistory>)]
|
||||
pub history: Vec<DelayHistory>,
|
||||
#[serde(serialize_with = "serialize_proxy_latency_history")]
|
||||
#[specta(type = HashMap<String, ProxyLatencyHistory<'static>>)]
|
||||
pub extra: HashMap<String, Extra>,
|
||||
#[serde(flatten)]
|
||||
pub capabilities: ProxyCapabilities,
|
||||
pub members: Vec<ProxyMemberRef>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize)]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, specta::Type)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProxyNodeView {
|
||||
pub record_id: String,
|
||||
pub name: String,
|
||||
#[serde(rename = "type")]
|
||||
#[specta(type = String)]
|
||||
pub proxy_type: ProxyType,
|
||||
pub alive: bool,
|
||||
#[specta(type = Vec<crate::ipc::DelayHistory>)]
|
||||
pub history: Vec<DelayHistory>,
|
||||
#[serde(serialize_with = "serialize_proxy_latency_history")]
|
||||
#[specta(type = HashMap<String, ProxyLatencyHistory<'static>>)]
|
||||
pub extra: HashMap<String, Extra>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
@@ -104,7 +132,7 @@ pub struct ProxyNodeView {
|
||||
pub source: ProxyNodeSource,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize)]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, specta::Type)]
|
||||
#[serde(tag = "kind", rename_all = "camelCase")]
|
||||
pub enum ProxyNodeSource {
|
||||
Core {
|
||||
@@ -119,7 +147,7 @@ pub enum ProxyNodeSource {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize)]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, specta::Type)]
|
||||
#[serde(tag = "kind", rename_all = "camelCase")]
|
||||
pub enum ProxyMemberRef {
|
||||
Group {
|
||||
@@ -136,7 +164,7 @@ pub enum ProxyMemberRef {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, specta::Type)]
|
||||
pub enum ProxyMemberUnresolvedReason {
|
||||
#[serde(rename = "missing")]
|
||||
Missing,
|
||||
@@ -146,7 +174,7 @@ pub enum ProxyMemberUnresolvedReason {
|
||||
ProviderUnavailable,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize)]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, specta::Type)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProxyProviderView {
|
||||
pub name: String,
|
||||
@@ -158,7 +186,7 @@ pub struct ProxyProviderView {
|
||||
pub proxy_record_ids: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, specta::Type)]
|
||||
pub enum ProxyProviderVehicleType {
|
||||
#[serde(rename = "HTTP")]
|
||||
Http,
|
||||
@@ -168,7 +196,7 @@ pub enum ProxyProviderVehicleType {
|
||||
Inline,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize)]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, specta::Type)]
|
||||
pub struct ProxySubscriptionInfo {
|
||||
pub upload: i64,
|
||||
pub download: i64,
|
||||
@@ -402,7 +430,6 @@ fn build_group(name: String, proxy: Proxy, resolver: &MemberResolver<'_>) -> Pro
|
||||
icon,
|
||||
now,
|
||||
test_url,
|
||||
alive,
|
||||
history,
|
||||
extra,
|
||||
udp,
|
||||
@@ -431,7 +458,6 @@ fn build_group(name: String, proxy: Proxy, resolver: &MemberResolver<'_>) -> Pro
|
||||
ProxyGroupView {
|
||||
name,
|
||||
proxy_type,
|
||||
alive,
|
||||
now,
|
||||
fixed,
|
||||
hidden,
|
||||
@@ -456,7 +482,6 @@ fn build_node(record_id: String, name: String, proxy: Proxy, source: ProxyNodeSo
|
||||
hidden,
|
||||
icon,
|
||||
test_url,
|
||||
alive,
|
||||
history,
|
||||
extra,
|
||||
udp,
|
||||
@@ -472,7 +497,6 @@ fn build_node(record_id: String, name: String, proxy: Proxy, source: ProxyNodeSo
|
||||
record_id,
|
||||
name,
|
||||
proxy_type,
|
||||
alive,
|
||||
history,
|
||||
extra,
|
||||
id,
|
||||
|
||||
@@ -14,13 +14,13 @@ pub enum ServiceHealth {
|
||||
}
|
||||
|
||||
impl ServiceHealth {
|
||||
const fn kind(&self) -> &'static str {
|
||||
const fn kind(&self) -> ServiceHealthKind {
|
||||
match self {
|
||||
Self::Unknown => "unknown",
|
||||
Self::Ready => "ready",
|
||||
Self::NotInstalled => "notInstalled",
|
||||
Self::VersionMismatch => "versionMismatch",
|
||||
Self::Unavailable(_) => "unavailable",
|
||||
Self::Unknown => ServiceHealthKind::Unknown,
|
||||
Self::Ready => ServiceHealthKind::Ready,
|
||||
Self::NotInstalled => ServiceHealthKind::NotInstalled,
|
||||
Self::VersionMismatch => ServiceHealthKind::VersionMismatch,
|
||||
Self::Unavailable(_) => ServiceHealthKind::Unavailable,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ impl ServiceHealth {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, specta::Type)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum PendingAction {
|
||||
Install,
|
||||
@@ -118,11 +118,11 @@ impl RunState {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
#[derive(Debug, Clone, serde::Serialize, specta::Type)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RunStateView {
|
||||
pub mode: RunningMode,
|
||||
pub service: &'static str,
|
||||
pub service: ServiceHealthKind,
|
||||
pub service_unavailable_reason: Option<String>,
|
||||
pub pending_action: Option<PendingAction>,
|
||||
pub sidecar_allowed: bool,
|
||||
@@ -232,7 +232,7 @@ mod tests {
|
||||
|
||||
let view = run_state.to_view();
|
||||
|
||||
assert_eq!(view.service, "unavailable");
|
||||
assert_eq!(view.service, ServiceHealthKind::Unavailable);
|
||||
assert_eq!(view.service_unavailable_reason.as_deref(), Some("socket refused"));
|
||||
assert_eq!(view.pending_action, Some(PendingAction::Reinstall));
|
||||
assert!(view.tun_capable, "elevation alone makes TUN possible");
|
||||
@@ -244,7 +244,7 @@ mod tests {
|
||||
fn a_healthy_view_reports_no_unavailable_reason() {
|
||||
let view = state(ServiceHealth::Ready, false, false).to_view();
|
||||
|
||||
assert_eq!(view.service, "ready");
|
||||
assert_eq!(view.service, ServiceHealthKind::Ready);
|
||||
assert_eq!(view.service_unavailable_reason, None);
|
||||
assert!(view.service_usable);
|
||||
assert!(!view.service_needs_attention);
|
||||
@@ -350,3 +350,13 @@ mod tests {
|
||||
assert!(settled.tun_should_be_disabled(true));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, specta::Type)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ServiceHealthKind {
|
||||
Unknown,
|
||||
Ready,
|
||||
NotInstalled,
|
||||
VersionMismatch,
|
||||
Unavailable,
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ static PENDING_SERVICE_REPAIR_NOTICE: AtomicBool = AtomicBool::new(false);
|
||||
static PENDING_SERVICE_OWNER_NOTICE: Mutex<Option<String>> = Mutex::new(None);
|
||||
|
||||
/// Why the Service was unavailable when the core fell back to Sidecar.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, specta::Type)]
|
||||
#[serde(tag = "kind", content = "reason", rename_all = "camelCase")]
|
||||
pub enum ServiceFallbackNotice {
|
||||
Unavailable,
|
||||
|
||||
@@ -1160,7 +1160,15 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
|
||||
logging!(error, Type::Tray, "Failed to close all connections from tray: {err}");
|
||||
}
|
||||
}
|
||||
MenuIds::COPY_ENV => feat::copy_clash_env().await,
|
||||
MenuIds::COPY_ENV => {
|
||||
if let Err(err) = feat::copy_clash_env().await {
|
||||
logging!(
|
||||
error,
|
||||
Type::Tray,
|
||||
"Failed to copy proxy environment variables from tray: {err:#}"
|
||||
);
|
||||
}
|
||||
}
|
||||
MenuIds::CONF_DIR => {
|
||||
let _ = cmd::open_app_dir().await;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ impl SilentUpdater {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, specta::Type)]
|
||||
struct UpdateCacheMeta {
|
||||
version: String,
|
||||
downloaded_at: String,
|
||||
@@ -214,7 +214,7 @@ fn nsis_language_id(app_language: &str) -> &'static str {
|
||||
}
|
||||
|
||||
/// Same wire shape as the updater plugin's JS `DownloadEvent`.
|
||||
#[derive(Clone, Serialize)]
|
||||
#[derive(Clone, Serialize, specta::Type)]
|
||||
#[serde(tag = "event", content = "data")]
|
||||
pub enum DownloadEvent {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
||||
@@ -51,7 +51,7 @@ pub struct CoreConfigValidator {
|
||||
is_processing: AtomicBool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, specta::Type)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ValidationErrorKind {
|
||||
FileMissing,
|
||||
@@ -94,7 +94,7 @@ impl ValidationErrorKind {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, specta::Type)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ValidationSkipReason {
|
||||
Exiting,
|
||||
@@ -110,12 +110,18 @@ impl fmt::Display for ValidationSkipReason {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, specta::Type)]
|
||||
#[serde(tag = "status", rename_all = "camelCase")]
|
||||
pub enum ValidationOutcome {
|
||||
Valid,
|
||||
Invalid { kind: ValidationErrorKind, message: String },
|
||||
Skipped { reason: ValidationSkipReason },
|
||||
Invalid {
|
||||
kind: ValidationErrorKind,
|
||||
#[specta(type = std::string::String)]
|
||||
message: String,
|
||||
},
|
||||
Skipped {
|
||||
reason: ValidationSkipReason,
|
||||
},
|
||||
Busy,
|
||||
}
|
||||
|
||||
|
||||
@@ -16,10 +16,13 @@ use smartstring::alias::String;
|
||||
use std::path::PathBuf;
|
||||
use tokio::fs;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[derive(Debug, Serialize, specta::Type)]
|
||||
pub struct LocalBackupFile {
|
||||
#[specta(type = std::string::String)]
|
||||
pub filename: String,
|
||||
#[specta(type = std::string::String)]
|
||||
pub path: String,
|
||||
#[specta(type = std::string::String)]
|
||||
pub last_modified: String,
|
||||
pub content_length: u64,
|
||||
}
|
||||
|
||||
@@ -34,12 +34,10 @@ static STAGING_GENERATION: AtomicU64 = AtomicU64::new(0);
|
||||
/// `.rollback` and `.old` are fixed paths, so two upgrades must not overlap.
|
||||
static UPGRADE_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
#[derive(Debug, serde::Serialize, specta::Type)]
|
||||
pub struct CoreUpgradeReport {
|
||||
/// False when the managed core was already at the latest version.
|
||||
pub upgraded: bool,
|
||||
pub from: std::string::String,
|
||||
pub to: std::string::String,
|
||||
}
|
||||
|
||||
pub async fn upgrade_core(force: bool) -> Result<CoreUpgradeReport> {
|
||||
@@ -62,11 +60,7 @@ pub async fn upgrade_core(force: bool) -> Result<CoreUpgradeReport> {
|
||||
logging!(debug, Type::Core, "core upgrade: latest version resolved via {proxy:?}");
|
||||
|
||||
if !force && installed == latest {
|
||||
return Ok(CoreUpgradeReport {
|
||||
upgraded: false,
|
||||
from: installed,
|
||||
to: latest,
|
||||
});
|
||||
return Ok(CoreUpgradeReport { upgraded: false });
|
||||
}
|
||||
|
||||
let package = download_package(proxy, alpha, &latest).await?;
|
||||
@@ -150,11 +144,7 @@ pub async fn upgrade_core(force: bool) -> Result<CoreUpgradeReport> {
|
||||
#[cfg(windows)]
|
||||
let _ = std::fs::remove_file(target.with_extension("old"));
|
||||
|
||||
Ok(CoreUpgradeReport {
|
||||
upgraded: true,
|
||||
from: installed,
|
||||
to: latest,
|
||||
})
|
||||
Ok(CoreUpgradeReport { upgraded: true })
|
||||
}
|
||||
|
||||
/// Whether this upgrade handed bytes to the Service's approved core directory.
|
||||
|
||||
@@ -15,11 +15,14 @@ use crate::{
|
||||
utils::dirs,
|
||||
};
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, specta::Type)]
|
||||
#[serde(tag = "status", rename_all = "snake_case")]
|
||||
pub enum DnsOverrideOutcome {
|
||||
Applied,
|
||||
ConfirmationRequired { source: String },
|
||||
ConfirmationRequired {
|
||||
#[specta(type = std::string::String)]
|
||||
source: String,
|
||||
},
|
||||
}
|
||||
|
||||
pub async fn set_dns_override(
|
||||
|
||||
@@ -8,10 +8,13 @@ use std::path::{Component, Path, PathBuf};
|
||||
use tokio::fs;
|
||||
use tokio::io::AsyncWriteExt as _;
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct IconInfo {
|
||||
#[specta(type = std::string::String)]
|
||||
name: String,
|
||||
#[specta(type = std::string::String)]
|
||||
previous_t: String,
|
||||
#[specta(type = std::string::String)]
|
||||
current_t: String,
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ use crate::{
|
||||
window_manager::WindowManager,
|
||||
},
|
||||
};
|
||||
use anyhow::Context as _;
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use std::env;
|
||||
use tauri_plugin_clipboard_manager::ClipboardExt as _;
|
||||
@@ -95,7 +96,7 @@ pub async fn toggle_tun_mode(not_save_file: Option<bool>) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn copy_clash_env() {
|
||||
pub async fn copy_clash_env() -> anyhow::Result<()> {
|
||||
let env_ip = env::var("CLASH_VERGE_REV_IP").ok();
|
||||
let verge_cfg = Config::verge().await.latest_arc();
|
||||
let ip = env_ip
|
||||
@@ -133,12 +134,11 @@ pub async fn copy_clash_env() {
|
||||
}
|
||||
"fish" => format!("set -x http_proxy {http_proxy}; set -x https_proxy {http_proxy}"),
|
||||
_ => {
|
||||
logging!(error, Type::ProxyMode, "copy_clash_env: Invalid env type! {env_type}");
|
||||
return;
|
||||
anyhow::bail!("Invalid environment variable format: {env_type}");
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = clipboard.write_text(&export_text) {
|
||||
logging!(error, Type::ProxyMode, "Failed to write to clipboard: {err}");
|
||||
}
|
||||
clipboard
|
||||
.write_text(export_text)
|
||||
.context("Failed to write proxy environment variables to clipboard")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
use crate::cmd;
|
||||
|
||||
pub fn builder() -> tauri_specta::Builder<tauri::Wry> {
|
||||
tauri_specta::Builder::new()
|
||||
.types(&platform_types())
|
||||
.commands(tauri_specta::collect_commands![
|
||||
tauri_plugin_clash_verge_sysinfo::commands::get_system_info,
|
||||
tauri_plugin_clash_verge_sysinfo::commands::get_app_uptime,
|
||||
tauri_plugin_clash_verge_sysinfo::commands::export_diagnostic_info::<tauri::Wry>,
|
||||
cmd::probe_listener,
|
||||
cmd::save_proxy_ports,
|
||||
cmd::get_sys_proxy,
|
||||
cmd::get_auto_proxy,
|
||||
cmd::get_embedded_server_port,
|
||||
cmd::open_app_dir,
|
||||
cmd::open_logs_dir,
|
||||
cmd::open_core_dir,
|
||||
cmd::get_network_interfaces,
|
||||
cmd::get_system_hostname,
|
||||
cmd::restart_app,
|
||||
cmd::install_update,
|
||||
cmd::cancel_update_download,
|
||||
cmd::restart_core,
|
||||
cmd::upgrade_clash_core,
|
||||
cmd::get_runtime_state,
|
||||
cmd::get_pending_failures,
|
||||
cmd::get_sidecar_failure,
|
||||
cmd::entry_lightweight_mode,
|
||||
cmd::install_service,
|
||||
cmd::uninstall_service,
|
||||
cmd::reinstall_service,
|
||||
cmd::repair_service,
|
||||
cmd::continue_with_sidecar,
|
||||
cmd::sync_runtime_providers,
|
||||
cmd::get_clash_info,
|
||||
cmd::patch_clash_config,
|
||||
cmd::patch_clash_mode,
|
||||
cmd::get_clash_mode,
|
||||
cmd::change_clash_core,
|
||||
cmd::get_runtime_config,
|
||||
cmd::get_proxy_view,
|
||||
cmd::get_runtime_yaml,
|
||||
cmd::get_runtime_logs,
|
||||
cmd::get_runtime_proxy_chain_config,
|
||||
cmd::update_proxy_chain_config_in_runtime,
|
||||
cmd::invoke_uwp_tool,
|
||||
cmd::copy_clash_env,
|
||||
cmd::sync_tray_proxy_selection,
|
||||
cmd::record_selected_node,
|
||||
cmd::forget_selected_node,
|
||||
cmd::save_dns_config,
|
||||
cmd::apply_dns_config,
|
||||
cmd::set_dns_override,
|
||||
cmd::take_dns_override_notice,
|
||||
cmd::take_service_fallback_notice,
|
||||
cmd::get_core_startup_error,
|
||||
cmd::take_service_repair_notice,
|
||||
cmd::take_service_owner_notice,
|
||||
cmd::take_discarded_keys_notice,
|
||||
cmd::get_dns_config_content,
|
||||
cmd::validate_dns_config,
|
||||
cmd::get_clash_logs,
|
||||
cmd::get_verge_config,
|
||||
cmd::patch_verge_config,
|
||||
cmd::test_delay,
|
||||
cmd::get_app_dir,
|
||||
cmd::copy_icon_file,
|
||||
cmd::download_icon_cache,
|
||||
cmd::open_devtools,
|
||||
cmd::exit_app,
|
||||
cmd::get_network_interfaces_info,
|
||||
cmd::get_profiles,
|
||||
cmd::enhance_profiles,
|
||||
cmd::patch_profiles_config,
|
||||
cmd::view_profile,
|
||||
cmd::patch_profile,
|
||||
cmd::create_profile,
|
||||
cmd::import_profile,
|
||||
cmd::reorder_profile,
|
||||
cmd::update_profile,
|
||||
cmd::delete_profile,
|
||||
cmd::read_profile_file,
|
||||
cmd::save_profile_file,
|
||||
cmd::get_next_update_time,
|
||||
cmd::create_local_backup,
|
||||
cmd::list_local_backup,
|
||||
cmd::delete_local_backup,
|
||||
cmd::restore_local_backup,
|
||||
cmd::import_local_backup,
|
||||
cmd::export_local_backup,
|
||||
cmd::create_webdav_backup,
|
||||
cmd::save_webdav_config,
|
||||
cmd::list_webdav_backup,
|
||||
cmd::delete_webdav_backup,
|
||||
cmd::restore_webdav_backup,
|
||||
cmd::get_unlock_items,
|
||||
cmd::check_media_unlock,
|
||||
cmd::check_media_unlock_item,
|
||||
])
|
||||
.error_handling(tauri_specta::ErrorHandlingMode::Throw)
|
||||
.dangerously_cast_bigints_to_number()
|
||||
.typ::<crate::core::notify::notification::VergeEventPayloads>()
|
||||
}
|
||||
|
||||
// Tauri transports YAML values as JSON; keep YAML storage out of the IPC type graph.
|
||||
#[derive(serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
#[serde(transparent)]
|
||||
pub struct YamlMapping(#[specta(type = std::collections::HashMap<String, JsonValue>)] pub serde_yaml_ng::Mapping);
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
#[serde(transparent)]
|
||||
pub struct YamlValue(#[specta(type = JsonValue)] pub serde_yaml_ng::Value);
|
||||
|
||||
#[derive(serde::Serialize, specta::Type)]
|
||||
pub struct WebDavFile {
|
||||
pub href: String,
|
||||
#[specta(type = String)]
|
||||
pub last_modified: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
impl From<reqwest_dav::list_cmd::ListFile> for WebDavFile {
|
||||
fn from(file: reqwest_dav::list_cmd::ListFile) -> Self {
|
||||
Self {
|
||||
href: file.href,
|
||||
last_modified: file.last_modified,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, specta::Type)]
|
||||
pub struct NetworkInterfaceView {
|
||||
pub name: String,
|
||||
pub addr: Vec<InterfaceAddress>,
|
||||
pub mac_addr: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum InterfaceAddress {
|
||||
V4(IpAddress),
|
||||
V6(IpAddress),
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct IpAddress {
|
||||
pub ip: String,
|
||||
}
|
||||
|
||||
impl From<network_interface::NetworkInterface> for NetworkInterfaceView {
|
||||
fn from(interface: network_interface::NetworkInterface) -> Self {
|
||||
Self {
|
||||
name: interface.name,
|
||||
addr: interface
|
||||
.addr
|
||||
.into_iter()
|
||||
.map(|address| match address {
|
||||
network_interface::Addr::V4(address) => InterfaceAddress::V4(IpAddress {
|
||||
ip: address.ip.to_string(),
|
||||
}),
|
||||
network_interface::Addr::V6(address) => InterfaceAddress::V6(IpAddress {
|
||||
ip: address.ip.to_string(),
|
||||
}),
|
||||
})
|
||||
.collect(),
|
||||
mac_addr: interface.mac_addr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct DelayHistory {
|
||||
pub time: String,
|
||||
pub delay: u16,
|
||||
}
|
||||
|
||||
fn platform_types() -> specta::Types {
|
||||
use specta::datatype::{DataType, Fields};
|
||||
let mut types = specta::Types::default().register::<crate::config::IVerge>();
|
||||
<PlatformVergeFields as specta::Type>::definition(&mut types);
|
||||
let platform = types
|
||||
.into_unsorted_iter()
|
||||
.find(|ty| ty.name == "PlatformVergeFields")
|
||||
.and_then(|ty| ty.ty.clone());
|
||||
let Some(DataType::Struct(platform)) = platform else {
|
||||
unreachable!()
|
||||
};
|
||||
let Fields::Named(platform) = platform.fields else {
|
||||
unreachable!()
|
||||
};
|
||||
types.iter_mut(|ty| {
|
||||
if ty.name != "IVerge" {
|
||||
return;
|
||||
}
|
||||
let Some(DataType::Struct(config)) = &mut ty.ty else {
|
||||
return;
|
||||
};
|
||||
let Fields::Named(fields) = &mut config.fields else {
|
||||
return;
|
||||
};
|
||||
fields
|
||||
.fields
|
||||
.retain(|(name, _)| !platform.fields.iter().any(|(platform_name, _)| name == platform_name));
|
||||
fields.fields.extend(platform.fields.iter().cloned());
|
||||
});
|
||||
types
|
||||
}
|
||||
|
||||
// These fields exist only on some hosts; IPC generation covers every desktop target.
|
||||
#[derive(serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
struct PlatformVergeFields {
|
||||
#[specta(optional)]
|
||||
tray_icon: Option<String>,
|
||||
#[specta(optional)]
|
||||
verge_redir_port: Option<u16>,
|
||||
#[specta(optional)]
|
||||
verge_redir_enabled: Option<bool>,
|
||||
#[specta(optional)]
|
||||
verge_tproxy_port: Option<u16>,
|
||||
#[specta(optional)]
|
||||
verge_tproxy_enabled: Option<bool>,
|
||||
#[specta(optional)]
|
||||
enable_tray_speed: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
#[serde(untagged)]
|
||||
pub enum JsonValue {
|
||||
Null(()),
|
||||
Bool(bool),
|
||||
Number(f64),
|
||||
String(String),
|
||||
// Specta resolves fields in nested functions, where Self is unavailable.
|
||||
Array(#[specta(type = Vec<JsonValue>)] Vec<Self>),
|
||||
Object(#[specta(type = std::collections::HashMap<String, JsonValue>)] std::collections::HashMap<String, Self>),
|
||||
}
|
||||
+5
-97
@@ -7,6 +7,7 @@ mod constants;
|
||||
mod core;
|
||||
mod enhance;
|
||||
mod feat;
|
||||
pub mod ipc;
|
||||
mod process;
|
||||
pub mod utils;
|
||||
|
||||
@@ -26,9 +27,6 @@ use tauri_plugin_deep_link::DeepLinkExt as _;
|
||||
|
||||
pub static APP_HANDLE: OnceLock<AppHandle> = OnceLock::new();
|
||||
|
||||
// Re-exported for src/bin/frontend-contract.rs; the wire contract stays owned
|
||||
// by core::notify.
|
||||
pub use crate::core::notify::frontend_wire_contract;
|
||||
/// Application initialization helper functions
|
||||
mod app_init {
|
||||
use super::*;
|
||||
@@ -127,100 +125,10 @@ mod app_init {
|
||||
}
|
||||
|
||||
pub fn generate_handlers() -> impl Fn(tauri::ipc::Invoke<tauri::Wry>) -> 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,
|
||||
tauri_plugin_clash_verge_sysinfo::commands::export_diagnostic_info,
|
||||
cmd::probe_listener,
|
||||
cmd::save_proxy_ports,
|
||||
cmd::get_sys_proxy,
|
||||
cmd::get_auto_proxy,
|
||||
cmd::get_embedded_server_port,
|
||||
cmd::open_app_dir,
|
||||
cmd::open_logs_dir,
|
||||
cmd::open_core_dir,
|
||||
cmd::get_network_interfaces,
|
||||
cmd::get_system_hostname,
|
||||
cmd::restart_app,
|
||||
cmd::install_update,
|
||||
cmd::cancel_update_download,
|
||||
cmd::restart_core,
|
||||
cmd::upgrade_clash_core,
|
||||
cmd::get_runtime_state,
|
||||
cmd::get_pending_failures,
|
||||
cmd::get_sidecar_failure,
|
||||
cmd::entry_lightweight_mode,
|
||||
cmd::install_service,
|
||||
cmd::uninstall_service,
|
||||
cmd::reinstall_service,
|
||||
cmd::repair_service,
|
||||
cmd::continue_with_sidecar,
|
||||
cmd::sync_runtime_providers,
|
||||
cmd::get_clash_info,
|
||||
cmd::patch_clash_config,
|
||||
cmd::patch_clash_mode,
|
||||
cmd::get_clash_mode,
|
||||
cmd::change_clash_core,
|
||||
cmd::get_runtime_config,
|
||||
cmd::get_proxy_view,
|
||||
cmd::get_runtime_yaml,
|
||||
cmd::get_runtime_logs,
|
||||
cmd::get_runtime_proxy_chain_config,
|
||||
cmd::update_proxy_chain_config_in_runtime,
|
||||
cmd::invoke_uwp_tool,
|
||||
cmd::copy_clash_env,
|
||||
cmd::sync_tray_proxy_selection,
|
||||
cmd::record_selected_node,
|
||||
cmd::forget_selected_node,
|
||||
cmd::save_dns_config,
|
||||
cmd::apply_dns_config,
|
||||
cmd::set_dns_override,
|
||||
cmd::take_dns_override_notice,
|
||||
cmd::take_service_fallback_notice,
|
||||
cmd::get_core_startup_error,
|
||||
cmd::take_service_repair_notice,
|
||||
cmd::take_service_owner_notice,
|
||||
cmd::take_discarded_keys_notice,
|
||||
cmd::get_dns_config_content,
|
||||
cmd::validate_dns_config,
|
||||
cmd::get_clash_logs,
|
||||
cmd::get_verge_config,
|
||||
cmd::patch_verge_config,
|
||||
cmd::test_delay,
|
||||
cmd::get_app_dir,
|
||||
cmd::copy_icon_file,
|
||||
cmd::download_icon_cache,
|
||||
cmd::open_devtools,
|
||||
cmd::exit_app,
|
||||
cmd::get_network_interfaces_info,
|
||||
cmd::get_profiles,
|
||||
cmd::enhance_profiles,
|
||||
cmd::patch_profiles_config,
|
||||
cmd::view_profile,
|
||||
cmd::patch_profile,
|
||||
cmd::create_profile,
|
||||
cmd::import_profile,
|
||||
cmd::reorder_profile,
|
||||
cmd::update_profile,
|
||||
cmd::delete_profile,
|
||||
cmd::read_profile_file,
|
||||
cmd::save_profile_file,
|
||||
cmd::get_next_update_time,
|
||||
cmd::create_local_backup,
|
||||
cmd::list_local_backup,
|
||||
cmd::delete_local_backup,
|
||||
cmd::restore_local_backup,
|
||||
cmd::import_local_backup,
|
||||
cmd::export_local_backup,
|
||||
cmd::create_webdav_backup,
|
||||
cmd::save_webdav_config,
|
||||
cmd::list_webdav_backup,
|
||||
cmd::delete_webdav_backup,
|
||||
cmd::restore_webdav_backup,
|
||||
cmd::get_unlock_items,
|
||||
cmd::check_media_unlock,
|
||||
cmd::check_media_unlock_item,
|
||||
]
|
||||
{
|
||||
let builder = crate::ipc::builder();
|
||||
move |invoke| builder.invoke_handler()(invoke)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user