Merge branch 'refactor'
This commit is contained in:
commit
3a37075e71
44
src-tauri/Cargo.lock
generated
44
src-tauri/Cargo.lock
generated
@ -488,9 +488,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.22"
|
||||
version = "0.4.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
|
||||
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
@ -985,9 +985,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c"
|
||||
checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
|
||||
dependencies = [
|
||||
"block-buffer 0.10.3",
|
||||
"crypto-common",
|
||||
@ -1860,9 +1860,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.24.4"
|
||||
version = "0.24.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd8e4fb07cf672b1642304e731ef8a6a4c7891d67bb4fd4f5ce58cd6ed86803c"
|
||||
checksum = "69b7ea949b537b0fd0af141fff8c77690f2ce96f4f41f042ccb6c69c6c965945"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder",
|
||||
@ -3235,9 +3235,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.12"
|
||||
version = "0.11.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc"
|
||||
checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
@ -3670,7 +3670,7 @@ checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cpufeatures",
|
||||
"digest 0.10.5",
|
||||
"digest 0.10.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3681,7 +3681,7 @@ checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cpufeatures",
|
||||
"digest 0.10.5",
|
||||
"digest 0.10.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3705,7 +3705,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cpufeatures",
|
||||
"digest 0.10.5",
|
||||
"digest 0.10.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3955,9 +3955,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tao"
|
||||
version = "0.15.3"
|
||||
version = "0.15.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42c460173627564bde252ca5ebf346ba5b37c5cee1a445782bacc8e9b8d38b5e"
|
||||
checksum = "a0382fcc02b87420a0d024ff6ec2dbfccdccafb46de8c03fb07dbf2f36810a42"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cairo-rs",
|
||||
@ -3995,7 +3995,7 @@ dependencies = [
|
||||
"scopeguard",
|
||||
"serde",
|
||||
"unicode-segmentation",
|
||||
"uuid 1.2.1",
|
||||
"uuid 1.2.2",
|
||||
"windows 0.39.0",
|
||||
"windows-implement",
|
||||
"x11-dl",
|
||||
@ -4060,7 +4060,7 @@ dependencies = [
|
||||
"time 0.3.17",
|
||||
"tokio",
|
||||
"url",
|
||||
"uuid 1.2.1",
|
||||
"uuid 1.2.2",
|
||||
"webkit2gtk",
|
||||
"webview2-com",
|
||||
"windows 0.39.0",
|
||||
@ -4105,7 +4105,7 @@ dependencies = [
|
||||
"tauri-utils",
|
||||
"thiserror",
|
||||
"time 0.3.17",
|
||||
"uuid 1.2.1",
|
||||
"uuid 1.2.2",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
@ -4138,7 +4138,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tauri-utils",
|
||||
"thiserror",
|
||||
"uuid 1.2.1",
|
||||
"uuid 1.2.2",
|
||||
"webview2-com",
|
||||
"windows 0.39.0",
|
||||
]
|
||||
@ -4156,7 +4156,7 @@ dependencies = [
|
||||
"raw-window-handle",
|
||||
"tauri-runtime",
|
||||
"tauri-utils",
|
||||
"uuid 1.2.1",
|
||||
"uuid 1.2.2",
|
||||
"webkit2gtk",
|
||||
"webview2-com",
|
||||
"windows 0.39.0",
|
||||
@ -4708,9 +4708,9 @@ checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.2.1"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "feb41e78f93363bb2df8b0e86a2ca30eed7806ea16ea0c790d757cf93f79be83"
|
||||
checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c"
|
||||
dependencies = [
|
||||
"getrandom 0.2.8",
|
||||
]
|
||||
@ -5358,9 +5358,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wry"
|
||||
version = "0.22.0"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "923d297b203eae65b095af16c02978b7932be1968012b4da7138390edf34dea5"
|
||||
checksum = "e0fd80bb2bd8e8eae26d59c5164e70233f29f7593cb886a958024a4fd8b5cd21"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"block",
|
||||
|
@ -1,258 +1,179 @@
|
||||
use crate::{
|
||||
core::Core,
|
||||
data::{ClashInfo, Data, PrfItem, PrfOption, Profiles, Verge},
|
||||
config::*,
|
||||
core::*,
|
||||
feat,
|
||||
utils::{dirs, help},
|
||||
};
|
||||
use crate::{log_if_err, ret_err, wrap_err};
|
||||
use anyhow::Result;
|
||||
use crate::{ret_err, wrap_err};
|
||||
use anyhow::{Context, Result};
|
||||
use serde_yaml::Mapping;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use sysproxy::Sysproxy;
|
||||
|
||||
type CmdResult<T = ()> = Result<T, String>;
|
||||
|
||||
/// get all profiles from `profiles.yaml`
|
||||
#[tauri::command]
|
||||
pub fn get_profiles() -> CmdResult<Profiles> {
|
||||
let global = Data::global();
|
||||
let profiles = global.profiles.lock();
|
||||
Ok(profiles.clone())
|
||||
pub fn get_profiles() -> CmdResult<IProfiles> {
|
||||
Ok(Config::profiles().data().clone())
|
||||
}
|
||||
|
||||
/// manually exec enhanced profile
|
||||
#[tauri::command]
|
||||
pub fn enhance_profiles() -> CmdResult {
|
||||
let core = Core::global();
|
||||
wrap_err!(core.activate())
|
||||
}
|
||||
|
||||
/// import the profile from url
|
||||
/// and save to `profiles.yaml`
|
||||
#[tauri::command]
|
||||
pub async fn import_profile(url: String, option: Option<PrfOption>) -> CmdResult {
|
||||
let item = wrap_err!(PrfItem::from_url(&url, None, None, option).await)?;
|
||||
|
||||
let global = Data::global();
|
||||
let mut profiles = global.profiles.lock();
|
||||
wrap_err!(profiles.append_item(item))
|
||||
}
|
||||
|
||||
/// new a profile
|
||||
/// append a temp profile item file to the `profiles` dir
|
||||
/// view the temp profile file by using vscode or other editor
|
||||
#[tauri::command]
|
||||
pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResult {
|
||||
let item = wrap_err!(PrfItem::from(item, file_data).await)?;
|
||||
|
||||
let global = Data::global();
|
||||
let mut profiles = global.profiles.lock();
|
||||
wrap_err!(profiles.append_item(item))
|
||||
}
|
||||
|
||||
/// Update the profile
|
||||
#[tauri::command]
|
||||
pub async fn update_profile(index: String, option: Option<PrfOption>) -> CmdResult {
|
||||
let core = Core::global();
|
||||
wrap_err!(core.update_profile_item(index, option).await)
|
||||
}
|
||||
|
||||
/// change the current profile
|
||||
#[tauri::command]
|
||||
pub fn select_profile(index: String) -> CmdResult {
|
||||
let global = Data::global();
|
||||
let mut profiles = global.profiles.lock();
|
||||
wrap_err!(profiles.put_current(index))?;
|
||||
drop(profiles);
|
||||
|
||||
let core = Core::global();
|
||||
wrap_err!(core.activate())
|
||||
}
|
||||
|
||||
/// change the profile chain
|
||||
#[tauri::command]
|
||||
pub fn change_profile_chain(chain: Option<Vec<String>>) -> CmdResult {
|
||||
let global = Data::global();
|
||||
let mut profiles = global.profiles.lock();
|
||||
wrap_err!(profiles.put_chain(chain))?;
|
||||
drop(profiles);
|
||||
|
||||
let core = Core::global();
|
||||
wrap_err!(core.activate())
|
||||
}
|
||||
|
||||
/// change the profile valid fields
|
||||
#[tauri::command]
|
||||
pub fn change_profile_valid(valid: Option<Vec<String>>) -> CmdResult {
|
||||
let global = Data::global();
|
||||
let mut profiles = global.profiles.lock();
|
||||
wrap_err!(profiles.put_valid(valid))?;
|
||||
drop(profiles);
|
||||
|
||||
let core = Core::global();
|
||||
wrap_err!(core.activate())
|
||||
}
|
||||
|
||||
/// delete profile item
|
||||
#[tauri::command]
|
||||
pub fn delete_profile(index: String) -> CmdResult {
|
||||
let global = Data::global();
|
||||
let mut profiles = global.profiles.lock();
|
||||
if wrap_err!(profiles.delete_item(index))? {
|
||||
drop(profiles);
|
||||
|
||||
let core = Core::global();
|
||||
log_if_err!(core.activate());
|
||||
}
|
||||
pub async fn enhance_profiles() -> CmdResult {
|
||||
wrap_err!(CoreManager::global().update_config().await)?;
|
||||
handle::Handle::refresh_clash();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// patch the profile config
|
||||
#[deprecated]
|
||||
#[tauri::command]
|
||||
pub fn patch_profile(index: String, profile: PrfItem) -> CmdResult {
|
||||
let global = Data::global();
|
||||
let mut profiles = global.profiles.lock();
|
||||
wrap_err!(profiles.patch_item(index, profile))?;
|
||||
drop(profiles);
|
||||
|
||||
// update cron task
|
||||
let core = Core::global();
|
||||
let mut timer = core.timer.lock();
|
||||
wrap_err!(timer.refresh())
|
||||
pub async fn import_profile(url: String, option: Option<PrfOption>) -> CmdResult {
|
||||
let item = wrap_err!(PrfItem::from_url(&url, None, None, option).await)?;
|
||||
wrap_err!(Config::profiles().data().append_item(item))
|
||||
}
|
||||
|
||||
/// run vscode command to edit the profile
|
||||
#[tauri::command]
|
||||
pub fn view_profile(index: String) -> CmdResult {
|
||||
let global = Data::global();
|
||||
let profiles = global.profiles.lock();
|
||||
let item = wrap_err!(profiles.get_item(&index))?;
|
||||
pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResult {
|
||||
let item = wrap_err!(PrfItem::from(item, file_data).await)?;
|
||||
wrap_err!(Config::profiles().data().append_item(item))
|
||||
}
|
||||
|
||||
let file = item.file.clone();
|
||||
if file.is_none() {
|
||||
ret_err!("file is null");
|
||||
#[tauri::command]
|
||||
pub async fn update_profile(index: String, option: Option<PrfOption>) -> CmdResult {
|
||||
wrap_err!(feat::update_profile(index, option).await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn delete_profile(index: String) -> CmdResult {
|
||||
let should_update = wrap_err!({ Config::profiles().data().delete_item(index) })?;
|
||||
if should_update {
|
||||
wrap_err!(CoreManager::global().update_config().await)?;
|
||||
handle::Handle::refresh_clash();
|
||||
}
|
||||
|
||||
let path = dirs::app_profiles_dir().join(file.unwrap());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 修改profiles的
|
||||
#[tauri::command]
|
||||
pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult {
|
||||
wrap_err!({ Config::profiles().draft().patch_config(profiles) })?;
|
||||
|
||||
match CoreManager::global().update_config().await {
|
||||
Ok(_) => {
|
||||
handle::Handle::refresh_clash();
|
||||
Config::profiles().apply();
|
||||
wrap_err!(Config::profiles().data().save_file())?;
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
Config::profiles().discard();
|
||||
log::error!(target: "app", "{err}");
|
||||
Err(format!("{err}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 修改某个profile item的
|
||||
#[tauri::command]
|
||||
pub fn patch_profile(index: String, profile: PrfItem) -> CmdResult {
|
||||
wrap_err!(Config::profiles().data().patch_item(index, profile))?;
|
||||
wrap_err!(timer::Timer::global().refresh())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn view_profile(index: String) -> CmdResult {
|
||||
let file = {
|
||||
wrap_err!(Config::profiles().latest().get_item(&index))?
|
||||
.file
|
||||
.clone()
|
||||
.ok_or("the file field is null")
|
||||
}?;
|
||||
|
||||
let path = wrap_err!(dirs::app_profiles_dir())?.join(file);
|
||||
if !path.exists() {
|
||||
ret_err!("file not found");
|
||||
ret_err!("the file not found");
|
||||
}
|
||||
|
||||
wrap_err!(help::open_file(path))
|
||||
}
|
||||
|
||||
/// read the profile item file data
|
||||
#[tauri::command]
|
||||
pub fn read_profile_file(index: String) -> CmdResult<String> {
|
||||
let global = Data::global();
|
||||
let profiles = global.profiles.lock();
|
||||
let profiles = Config::profiles();
|
||||
let profiles = profiles.latest();
|
||||
let item = wrap_err!(profiles.get_item(&index))?;
|
||||
let data = wrap_err!(item.read_file())?;
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
/// save the profile item file data
|
||||
#[tauri::command]
|
||||
pub fn save_profile_file(index: String, file_data: Option<String>) -> CmdResult {
|
||||
if file_data.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let global = Data::global();
|
||||
let profiles = global.profiles.lock();
|
||||
let profiles = Config::profiles();
|
||||
let profiles = profiles.latest();
|
||||
let item = wrap_err!(profiles.get_item(&index))?;
|
||||
wrap_err!(item.save_file(file_data.unwrap()))
|
||||
}
|
||||
|
||||
/// get the clash core info from the state
|
||||
/// the caller can also get the infomation by clash's api
|
||||
#[tauri::command]
|
||||
pub fn get_clash_info() -> CmdResult<ClashInfo> {
|
||||
let global = Data::global();
|
||||
let clash = global.clash.lock();
|
||||
Ok(clash.info.clone())
|
||||
pub fn get_clash_info() -> CmdResult<ClashInfoN> {
|
||||
wrap_err!(Config::clash().latest().get_info())
|
||||
}
|
||||
|
||||
/// get the runtime clash config mapping
|
||||
#[tauri::command]
|
||||
pub fn get_runtime_config() -> CmdResult<Option<Mapping>> {
|
||||
let core = Core::global();
|
||||
let rt = core.runtime.lock();
|
||||
Ok(rt.config.clone())
|
||||
Ok(Config::runtime().latest().config.clone())
|
||||
}
|
||||
|
||||
/// get the runtime clash config yaml string
|
||||
#[tauri::command]
|
||||
pub fn get_runtime_yaml() -> CmdResult<Option<String>> {
|
||||
let core = Core::global();
|
||||
let rt = core.runtime.lock();
|
||||
Ok(rt.config_yaml.clone())
|
||||
pub fn get_runtime_yaml() -> CmdResult<String> {
|
||||
let runtime = Config::runtime();
|
||||
let runtime = runtime.latest();
|
||||
let config = runtime.config.as_ref();
|
||||
wrap_err!(config
|
||||
.ok_or(anyhow::anyhow!("failed to parse config to yaml file"))
|
||||
.and_then(
|
||||
|config| serde_yaml::to_string(config).context("failed to convert config to yaml")
|
||||
))
|
||||
}
|
||||
|
||||
/// get the runtime config exists keys
|
||||
#[tauri::command]
|
||||
pub fn get_runtime_exists() -> CmdResult<Vec<String>> {
|
||||
let core = Core::global();
|
||||
let rt = core.runtime.lock();
|
||||
Ok(rt.exists_keys.clone())
|
||||
Ok(Config::runtime().latest().exists_keys.clone())
|
||||
}
|
||||
|
||||
/// get the runtime enhanced chain log
|
||||
#[tauri::command]
|
||||
pub fn get_runtime_logs() -> CmdResult<HashMap<String, Vec<(String, String)>>> {
|
||||
let core = Core::global();
|
||||
let rt = core.runtime.lock();
|
||||
Ok(rt.chain_logs.clone())
|
||||
}
|
||||
|
||||
/// update the clash core config
|
||||
/// after putting the change to the clash core
|
||||
/// then we should save the latest config
|
||||
#[tauri::command]
|
||||
pub fn patch_clash_config(payload: Mapping) -> CmdResult {
|
||||
let core = Core::global();
|
||||
wrap_err!(core.patch_clash(payload))
|
||||
Ok(Config::runtime().latest().chain_logs.clone())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_verge_config() -> CmdResult<Verge> {
|
||||
let global = Data::global();
|
||||
let verge = global.verge.lock();
|
||||
Ok(verge.clone())
|
||||
}
|
||||
|
||||
/// patch the verge config
|
||||
/// this command only save the config and not responsible for other things
|
||||
#[tauri::command]
|
||||
pub fn patch_verge_config(payload: Verge) -> CmdResult {
|
||||
let core = Core::global();
|
||||
wrap_err!(core.patch_verge(payload))
|
||||
pub async fn patch_clash_config(payload: Mapping) -> CmdResult {
|
||||
wrap_err!(feat::patch_clash(payload).await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn update_hotkeys(hotkeys: Vec<String>) -> CmdResult {
|
||||
let core = Core::global();
|
||||
let mut hotkey = core.hotkey.lock();
|
||||
wrap_err!(hotkey.update(hotkeys))
|
||||
pub fn get_verge_config() -> CmdResult<IVerge> {
|
||||
Ok(Config::verge().data().clone())
|
||||
}
|
||||
|
||||
/// change clash core
|
||||
#[tauri::command]
|
||||
pub fn change_clash_core(clash_core: Option<String>) -> CmdResult {
|
||||
let core = Core::global();
|
||||
wrap_err!(core.change_core(clash_core))
|
||||
pub async fn patch_verge_config(payload: IVerge) -> CmdResult {
|
||||
wrap_err!(feat::patch_verge(payload).await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn change_clash_core(clash_core: Option<String>) -> CmdResult {
|
||||
wrap_err!(CoreManager::global().change_core(clash_core).await)
|
||||
}
|
||||
|
||||
/// restart the sidecar
|
||||
#[tauri::command]
|
||||
pub fn restart_sidecar() -> CmdResult {
|
||||
let core = Core::global();
|
||||
wrap_err!(core.restart_clash())
|
||||
}
|
||||
|
||||
/// kill all sidecars when update app
|
||||
#[tauri::command]
|
||||
pub fn kill_sidecar() {
|
||||
tauri::api::process::kill_children();
|
||||
pub async fn restart_sidecar() -> CmdResult {
|
||||
wrap_err!(CoreManager::global().run_core().await)
|
||||
}
|
||||
|
||||
/// get the system proxy
|
||||
@ -273,64 +194,44 @@ pub fn get_sys_proxy() -> CmdResult<Mapping> {
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_clash_logs() -> CmdResult<VecDeque<String>> {
|
||||
let core = Core::global();
|
||||
let service = core.service.lock();
|
||||
Ok(service.get_logs())
|
||||
Ok(logger::Logger::global().get_log())
|
||||
}
|
||||
|
||||
/// open app config dir
|
||||
#[tauri::command]
|
||||
pub fn open_app_dir() -> CmdResult<()> {
|
||||
let app_dir = dirs::app_home_dir();
|
||||
let app_dir = wrap_err!(dirs::app_home_dir())?;
|
||||
wrap_err!(open::that(app_dir))
|
||||
}
|
||||
|
||||
/// open logs dir
|
||||
#[tauri::command]
|
||||
pub fn open_logs_dir() -> CmdResult<()> {
|
||||
let log_dir = dirs::app_logs_dir();
|
||||
let log_dir = wrap_err!(dirs::app_logs_dir())?;
|
||||
wrap_err!(open::that(log_dir))
|
||||
}
|
||||
|
||||
/// open url
|
||||
#[tauri::command]
|
||||
pub fn open_web_url(url: String) -> CmdResult<()> {
|
||||
wrap_err!(open::that(url))
|
||||
}
|
||||
|
||||
/// service mode
|
||||
#[cfg(windows)]
|
||||
pub mod service {
|
||||
use super::*;
|
||||
use crate::core::win_service::JsonResponse;
|
||||
use crate::core::win_service;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn start_service() -> CmdResult<()> {
|
||||
wrap_err!(crate::core::Service::start_service().await)
|
||||
pub async fn check_service() -> CmdResult<win_service::JsonResponse> {
|
||||
wrap_err!(win_service::check_service().await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn stop_service() -> CmdResult<()> {
|
||||
wrap_err!(crate::core::Service::stop_service().await)
|
||||
pub async fn install_service() -> CmdResult {
|
||||
wrap_err!(win_service::install_service().await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn check_service() -> CmdResult<JsonResponse> {
|
||||
// no log
|
||||
match crate::core::Service::check_service().await {
|
||||
Ok(res) => Ok(res),
|
||||
Err(err) => Err(err.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn install_service() -> CmdResult<()> {
|
||||
wrap_err!(crate::core::Service::install_service().await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn uninstall_service() -> CmdResult<()> {
|
||||
wrap_err!(crate::core::Service::uninstall_service().await)
|
||||
pub async fn uninstall_service() -> CmdResult {
|
||||
wrap_err!(win_service::uninstall_service().await)
|
||||
}
|
||||
}
|
||||
|
||||
@ -339,23 +240,15 @@ pub mod service {
|
||||
use super::*;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn start_service() -> CmdResult<()> {
|
||||
pub async fn check_service() -> CmdResult {
|
||||
Ok(())
|
||||
}
|
||||
#[tauri::command]
|
||||
pub async fn stop_service() -> CmdResult<()> {
|
||||
pub async fn install_service() -> CmdResult {
|
||||
Ok(())
|
||||
}
|
||||
#[tauri::command]
|
||||
pub async fn check_service() -> CmdResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
#[tauri::command]
|
||||
pub async fn install_service() -> CmdResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
#[tauri::command]
|
||||
pub async fn uninstall_service() -> CmdResult<()> {
|
||||
pub async fn uninstall_service() -> CmdResult {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +1,70 @@
|
||||
use crate::utils::{config, dirs};
|
||||
use crate::utils::{dirs, help};
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_yaml::{Mapping, Value};
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct IClashTemp(pub Mapping);
|
||||
|
||||
impl IClashTemp {
|
||||
pub fn new() -> Self {
|
||||
match dirs::clash_path().and_then(|path| help::read_merge_mapping(&path)) {
|
||||
Ok(map) => Self(map),
|
||||
Err(err) => {
|
||||
log::error!(target: "app", "{err}");
|
||||
Self::template()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn template() -> Self {
|
||||
let mut map = Mapping::new();
|
||||
|
||||
map.insert("mixed-port".into(), 7890.into());
|
||||
map.insert("log-level".into(), "info".into());
|
||||
map.insert("allow-lan".into(), false.into());
|
||||
map.insert("mode".into(), "rule".into());
|
||||
map.insert("external-controller".into(), "127.0.0.1:9090".into());
|
||||
map.insert("secret".into(), "".into());
|
||||
|
||||
Self(map)
|
||||
}
|
||||
|
||||
pub fn patch_config(&mut self, patch: Mapping) {
|
||||
for (key, value) in patch.into_iter() {
|
||||
self.0.insert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save_config(&self) -> Result<()> {
|
||||
help::save_yaml(
|
||||
&dirs::clash_path()?,
|
||||
&self.0,
|
||||
Some("# Generated by Clash Verge"),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_info(&self) -> Result<ClashInfoN> {
|
||||
Ok(ClashInfoN::from(&self.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ClashInfo {
|
||||
pub struct ClashInfoN {
|
||||
/// clash sidecar status
|
||||
pub status: String,
|
||||
|
||||
/// clash core port
|
||||
pub port: Option<String>,
|
||||
|
||||
/// same as `external-controller`
|
||||
pub server: Option<String>,
|
||||
|
||||
/// clash secret
|
||||
pub secret: Option<String>,
|
||||
}
|
||||
|
||||
impl ClashInfo {
|
||||
impl ClashInfoN {
|
||||
/// parse the clash's config.yaml
|
||||
/// get some information
|
||||
pub fn from(config: &Mapping) -> ClashInfo {
|
||||
pub fn from(config: &Mapping) -> ClashInfoN {
|
||||
let key_port_1 = Value::from("mixed-port");
|
||||
let key_port_2 = Value::from("port");
|
||||
let key_server = Value::from("external-controller");
|
||||
@ -97,7 +140,7 @@ impl ClashInfo {
|
||||
_ => None,
|
||||
};
|
||||
|
||||
ClashInfo {
|
||||
ClashInfoN {
|
||||
status: format!("{status}"),
|
||||
port,
|
||||
server,
|
||||
@ -106,62 +149,52 @@ impl ClashInfo {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Clash {
|
||||
/// maintain the clash config
|
||||
pub config: Mapping,
|
||||
|
||||
/// some info
|
||||
pub info: ClashInfo,
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct IClash {
|
||||
pub mixed_port: Option<u16>,
|
||||
pub allow_lan: Option<bool>,
|
||||
pub log_level: Option<String>,
|
||||
pub ipv6: Option<bool>,
|
||||
pub mode: Option<String>,
|
||||
pub external_controller: Option<String>,
|
||||
pub secret: Option<String>,
|
||||
pub dns: Option<IClashDNS>,
|
||||
pub tun: Option<IClashTUN>,
|
||||
pub interface_name: Option<String>,
|
||||
}
|
||||
|
||||
impl Clash {
|
||||
pub fn new() -> Clash {
|
||||
let config = Clash::read_config();
|
||||
let info = ClashInfo::from(&config);
|
||||
|
||||
Clash { config, info }
|
||||
}
|
||||
|
||||
/// get clash config
|
||||
pub fn read_config() -> Mapping {
|
||||
config::read_merge_mapping(dirs::clash_path())
|
||||
}
|
||||
|
||||
/// save the clash config
|
||||
pub fn save_config(&self) -> Result<()> {
|
||||
config::save_yaml(
|
||||
dirs::clash_path(),
|
||||
&self.config,
|
||||
Some("# Default Config For Clash Core\n\n"),
|
||||
)
|
||||
}
|
||||
|
||||
/// patch update the clash config
|
||||
/// if the port is changed then return true
|
||||
pub fn patch_config(&mut self, patch: Mapping) -> Result<()> {
|
||||
let port_key = Value::from("mixed-port");
|
||||
let server_key = Value::from("external-controller");
|
||||
let secret_key = Value::from("secret");
|
||||
|
||||
let change_info = patch.contains_key(&port_key)
|
||||
|| patch.contains_key(&server_key)
|
||||
|| patch.contains_key(&secret_key);
|
||||
|
||||
for (key, value) in patch.into_iter() {
|
||||
self.config.insert(key, value);
|
||||
}
|
||||
|
||||
if change_info {
|
||||
self.info = ClashInfo::from(&self.config);
|
||||
}
|
||||
|
||||
self.save_config()
|
||||
}
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct IClashTUN {
|
||||
pub enable: Option<bool>,
|
||||
pub stack: Option<String>,
|
||||
pub auto_route: Option<bool>,
|
||||
pub auto_detect_interface: Option<bool>,
|
||||
pub dns_hijack: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl Default for Clash {
|
||||
fn default() -> Self {
|
||||
Clash::new()
|
||||
}
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct IClashDNS {
|
||||
pub enable: Option<bool>,
|
||||
pub listen: Option<String>,
|
||||
pub default_nameserver: Option<Vec<String>>,
|
||||
pub enhanced_mode: Option<String>,
|
||||
pub fake_ip_range: Option<String>,
|
||||
pub use_hosts: Option<bool>,
|
||||
pub fake_ip_filter: Option<Vec<String>>,
|
||||
pub nameserver: Option<Vec<String>>,
|
||||
pub fallback: Option<Vec<String>>,
|
||||
pub fallback_filter: Option<IClashFallbackFilter>,
|
||||
pub nameserver_policy: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct IClashFallbackFilter {
|
||||
pub geoip: Option<bool>,
|
||||
pub geoip_code: Option<String>,
|
||||
pub ipcidr: Option<Vec<String>>,
|
||||
pub domain: Option<Vec<String>>,
|
||||
}
|
111
src-tauri/src/config/config.rs
Normal file
111
src-tauri/src/config/config.rs
Normal file
@ -0,0 +1,111 @@
|
||||
use super::{Draft, IClashTemp, IProfiles, IRuntime, IVerge};
|
||||
use crate::{
|
||||
enhance,
|
||||
utils::{dirs, help},
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::{env::temp_dir, path::PathBuf};
|
||||
|
||||
pub const RUNTIME_CONFIG: &str = "clash-verge.yaml";
|
||||
pub const CHECK_CONFIG: &str = "clash-verge-check.yaml";
|
||||
|
||||
pub struct Config {
|
||||
clash_config: Draft<IClashTemp>,
|
||||
verge_config: Draft<IVerge>,
|
||||
profiles_config: Draft<IProfiles>,
|
||||
runtime_config: Draft<IRuntime>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn global() -> &'static Config {
|
||||
static CONFIG: OnceCell<Config> = OnceCell::new();
|
||||
|
||||
CONFIG.get_or_init(|| Config {
|
||||
clash_config: Draft::from(IClashTemp::new()),
|
||||
verge_config: Draft::from(IVerge::new()),
|
||||
profiles_config: Draft::from(IProfiles::new()),
|
||||
runtime_config: Draft::from(IRuntime::new()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn clash() -> Draft<IClashTemp> {
|
||||
Self::global().clash_config.clone()
|
||||
}
|
||||
|
||||
pub fn verge() -> Draft<IVerge> {
|
||||
Self::global().verge_config.clone()
|
||||
}
|
||||
|
||||
pub fn profiles() -> Draft<IProfiles> {
|
||||
Self::global().profiles_config.clone()
|
||||
}
|
||||
|
||||
pub fn runtime() -> Draft<IRuntime> {
|
||||
Self::global().runtime_config.clone()
|
||||
}
|
||||
|
||||
/// 初始化配置
|
||||
pub fn init_config() -> Result<()> {
|
||||
crate::log_err!(Self::generate());
|
||||
if let Err(err) = Self::generate_file(ConfigType::Run) {
|
||||
log::error!(target: "app", "{err}");
|
||||
|
||||
let runtime_path = dirs::app_home_dir()?.join(RUNTIME_CONFIG);
|
||||
// 如果不存在就将默认的clash文件拿过来
|
||||
if !runtime_path.exists() {
|
||||
help::save_yaml(
|
||||
&runtime_path,
|
||||
&Config::clash().latest().0,
|
||||
Some("# Clash Verge Runtime"),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 将配置丢到对应的文件中
|
||||
pub fn generate_file(typ: ConfigType) -> Result<PathBuf> {
|
||||
let path = match typ {
|
||||
ConfigType::Run => dirs::app_home_dir()?.join(RUNTIME_CONFIG),
|
||||
ConfigType::Check => temp_dir().join(CHECK_CONFIG),
|
||||
};
|
||||
|
||||
let runtime = Config::runtime();
|
||||
let runtime = runtime.latest();
|
||||
let config = runtime
|
||||
.config
|
||||
.as_ref()
|
||||
.ok_or(anyhow!("failed to get runtime config"))?;
|
||||
|
||||
help::save_yaml(&path, &config, Some("# Generated by Clash Verge"))?;
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
/// 生成配置存好
|
||||
pub fn generate() -> Result<()> {
|
||||
let clash_config = { Config::clash().latest().clone() };
|
||||
|
||||
let tun_mode = { Config::verge().latest().enable_tun_mode.clone() };
|
||||
let tun_mode = tun_mode.unwrap_or(false);
|
||||
|
||||
let pa = { Config::profiles().latest().gen_activate()? };
|
||||
|
||||
let (config, exists_keys, logs) =
|
||||
enhance::enhance_config(clash_config.0, pa.current, pa.chain, pa.valid, tun_mode);
|
||||
|
||||
*Config::runtime().draft() = IRuntime {
|
||||
config: Some(config),
|
||||
exists_keys,
|
||||
chain_logs: logs,
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ConfigType {
|
||||
Run,
|
||||
Check,
|
||||
}
|
127
src-tauri/src/config/draft.rs
Normal file
127
src-tauri/src/config/draft.rs
Normal file
@ -0,0 +1,127 @@
|
||||
use super::{IClashTemp, IProfiles, IRuntime, IVerge};
|
||||
use parking_lot::{MappedMutexGuard, Mutex, MutexGuard};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Draft<T: Clone + ToOwned> {
|
||||
inner: Arc<Mutex<(T, Option<T>)>>,
|
||||
}
|
||||
|
||||
macro_rules! draft_define {
|
||||
($id: ident) => {
|
||||
impl Draft<$id> {
|
||||
#[allow(unused)]
|
||||
pub fn data(&self) -> MappedMutexGuard<$id> {
|
||||
MutexGuard::map(self.inner.lock(), |guard| &mut guard.0)
|
||||
}
|
||||
|
||||
pub fn latest(&self) -> MappedMutexGuard<$id> {
|
||||
MutexGuard::map(self.inner.lock(), |inner| {
|
||||
if inner.1.is_none() {
|
||||
&mut inner.0
|
||||
} else {
|
||||
inner.1.as_mut().unwrap()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn draft(&self) -> MappedMutexGuard<$id> {
|
||||
MutexGuard::map(self.inner.lock(), |mut inner| {
|
||||
if inner.1.is_none() {
|
||||
inner.1 = Some(inner.0.clone());
|
||||
}
|
||||
|
||||
inner.1.as_mut().unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn apply(&self) -> Option<$id> {
|
||||
let mut inner = self.inner.lock();
|
||||
|
||||
match inner.1.take() {
|
||||
Some(draft) => {
|
||||
let old_value = inner.0.to_owned();
|
||||
inner.0 = draft.to_owned();
|
||||
Some(old_value)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn discard(&self) -> Option<$id> {
|
||||
let mut inner = self.inner.lock();
|
||||
inner.1.take()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$id> for Draft<$id> {
|
||||
fn from(data: $id) -> Self {
|
||||
Draft {
|
||||
inner: Arc::new(Mutex::new((data, None))),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// draft_define!(IClash);
|
||||
draft_define!(IClashTemp);
|
||||
draft_define!(IProfiles);
|
||||
draft_define!(IRuntime);
|
||||
draft_define!(IVerge);
|
||||
|
||||
#[test]
|
||||
fn test_draft() {
|
||||
let verge = IVerge {
|
||||
enable_auto_launch: Some(true),
|
||||
enable_tun_mode: Some(false),
|
||||
..IVerge::default()
|
||||
};
|
||||
|
||||
let draft = Draft::from(verge);
|
||||
|
||||
assert_eq!(draft.data().enable_auto_launch, Some(true));
|
||||
assert_eq!(draft.data().enable_tun_mode, Some(false));
|
||||
|
||||
assert_eq!(draft.draft().enable_auto_launch, Some(true));
|
||||
assert_eq!(draft.draft().enable_tun_mode, Some(false));
|
||||
|
||||
let mut d = draft.draft();
|
||||
d.enable_auto_launch = Some(false);
|
||||
d.enable_tun_mode = Some(true);
|
||||
drop(d);
|
||||
|
||||
assert_eq!(draft.data().enable_auto_launch, Some(true));
|
||||
assert_eq!(draft.data().enable_tun_mode, Some(false));
|
||||
|
||||
assert_eq!(draft.draft().enable_auto_launch, Some(false));
|
||||
assert_eq!(draft.draft().enable_tun_mode, Some(true));
|
||||
|
||||
assert_eq!(draft.latest().enable_auto_launch, Some(false));
|
||||
assert_eq!(draft.latest().enable_tun_mode, Some(true));
|
||||
|
||||
assert!(draft.apply().is_some());
|
||||
assert!(draft.apply().is_none());
|
||||
|
||||
assert_eq!(draft.data().enable_auto_launch, Some(false));
|
||||
assert_eq!(draft.data().enable_tun_mode, Some(true));
|
||||
|
||||
assert_eq!(draft.draft().enable_auto_launch, Some(false));
|
||||
assert_eq!(draft.draft().enable_tun_mode, Some(true));
|
||||
|
||||
let mut d = draft.draft();
|
||||
d.enable_auto_launch = Some(true);
|
||||
drop(d);
|
||||
|
||||
assert_eq!(draft.data().enable_auto_launch, Some(false));
|
||||
|
||||
assert_eq!(draft.draft().enable_auto_launch, Some(true));
|
||||
|
||||
assert!(draft.discard().is_some());
|
||||
|
||||
assert_eq!(draft.data().enable_auto_launch, Some(false));
|
||||
|
||||
assert!(draft.discard().is_none());
|
||||
|
||||
assert_eq!(draft.draft().enable_auto_launch, Some(false));
|
||||
}
|
@ -1,69 +1,15 @@
|
||||
mod field;
|
||||
mod merge;
|
||||
mod script;
|
||||
mod tun;
|
||||
mod clash;
|
||||
mod config;
|
||||
mod draft;
|
||||
mod prfitem;
|
||||
mod profiles;
|
||||
mod runtime;
|
||||
mod verge;
|
||||
|
||||
pub(self) use self::field::*;
|
||||
use self::merge::*;
|
||||
use self::script::*;
|
||||
use self::tun::*;
|
||||
use crate::data::ChainItem;
|
||||
use crate::data::ChainType;
|
||||
use serde_yaml::Mapping;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
|
||||
type ResultLog = Vec<(String, String)>;
|
||||
|
||||
pub fn enhance_config(
|
||||
clash_config: Mapping,
|
||||
profile_config: Mapping,
|
||||
chain: Vec<ChainItem>,
|
||||
valid: Vec<String>,
|
||||
tun_mode: bool,
|
||||
) -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
||||
let mut config = profile_config;
|
||||
let mut result_map = HashMap::new();
|
||||
let mut exists_keys = use_keys(&config);
|
||||
|
||||
let valid = use_valid_fields(valid);
|
||||
|
||||
chain.into_iter().for_each(|item| match item.data {
|
||||
ChainType::Merge(merge) => {
|
||||
exists_keys.extend(use_keys(&merge));
|
||||
config = use_merge(merge, config.to_owned());
|
||||
config = use_filter(config.to_owned(), &valid);
|
||||
}
|
||||
ChainType::Script(script) => {
|
||||
let mut logs = vec![];
|
||||
|
||||
match use_script(script, config.to_owned()) {
|
||||
Ok((res_config, res_logs)) => {
|
||||
exists_keys.extend(use_keys(&res_config));
|
||||
config = use_filter(res_config, &valid);
|
||||
logs.extend(res_logs);
|
||||
}
|
||||
Err(err) => logs.push(("exception".into(), err.to_string())),
|
||||
}
|
||||
|
||||
result_map.insert(item.uid, logs);
|
||||
}
|
||||
});
|
||||
|
||||
config = use_filter(config, &valid);
|
||||
|
||||
for (key, value) in clash_config.into_iter() {
|
||||
config.insert(key, value);
|
||||
}
|
||||
|
||||
let clash_fields = use_clash_fields();
|
||||
config = use_filter(config, &clash_fields);
|
||||
config = use_tun(config, tun_mode);
|
||||
config = use_sort(config);
|
||||
|
||||
let mut exists_set = HashSet::new();
|
||||
exists_set.extend(exists_keys.into_iter().filter(|s| clash_fields.contains(s)));
|
||||
exists_keys = exists_set.into_iter().collect();
|
||||
|
||||
(config, exists_keys, result_map)
|
||||
}
|
||||
pub use self::clash::*;
|
||||
pub use self::config::*;
|
||||
pub use self::draft::*;
|
||||
pub use self::prfitem::*;
|
||||
pub use self::profiles::*;
|
||||
pub use self::runtime::*;
|
||||
pub use self::verge::*;
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::utils::{config, dirs, help, tmpl};
|
||||
use crate::utils::{dirs, help, tmpl};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use reqwest::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -6,6 +6,8 @@ use serde_yaml::Mapping;
|
||||
use std::fs;
|
||||
use sysproxy::Sysproxy;
|
||||
|
||||
use super::Config;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct PrfItem {
|
||||
pub uid: Option<String>,
|
||||
@ -29,7 +31,7 @@ pub struct PrfItem {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub url: Option<String>,
|
||||
|
||||
/// selected infomation
|
||||
/// selected information
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub selected: Option<Vec<PrfSelected>>,
|
||||
|
||||
@ -170,7 +172,7 @@ impl PrfItem {
|
||||
selected: None,
|
||||
extra: None,
|
||||
option: None,
|
||||
updated: Some(help::get_now()),
|
||||
updated: Some(chrono::Local::now().timestamp() as usize),
|
||||
file_data: Some(file_data.unwrap_or(tmpl::ITEM_LOCAL.into())),
|
||||
})
|
||||
}
|
||||
@ -192,9 +194,9 @@ impl PrfItem {
|
||||
|
||||
// 使用软件自己的代理
|
||||
if self_proxy {
|
||||
let data = super::Data::global();
|
||||
let port = data.clash.lock().info.port.clone();
|
||||
let port = Config::clash().data().get_info()?.port;
|
||||
let port = port.ok_or(anyhow::anyhow!("failed to get clash info port"))?;
|
||||
|
||||
let proxy_scheme = format!("http://127.0.0.1:{port}");
|
||||
|
||||
if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) {
|
||||
@ -299,7 +301,7 @@ impl PrfItem {
|
||||
selected: None,
|
||||
extra,
|
||||
option,
|
||||
updated: Some(help::get_now()),
|
||||
updated: Some(chrono::Local::now().timestamp() as usize),
|
||||
file_data: Some(data),
|
||||
})
|
||||
}
|
||||
@ -320,13 +322,13 @@ impl PrfItem {
|
||||
selected: None,
|
||||
extra: None,
|
||||
option: None,
|
||||
updated: Some(help::get_now()),
|
||||
updated: Some(chrono::Local::now().timestamp() as usize),
|
||||
file_data: Some(tmpl::ITEM_MERGE.into()),
|
||||
})
|
||||
}
|
||||
|
||||
/// ## Script type (enhance)
|
||||
/// create the enhanced item by using javascript(browserjs)
|
||||
/// create the enhanced item by using javascript quick.js
|
||||
pub fn from_script(name: String, desc: String) -> Result<PrfItem> {
|
||||
let uid = help::get_uid("s");
|
||||
let file = format!("{uid}.js"); // js ext
|
||||
@ -341,7 +343,7 @@ impl PrfItem {
|
||||
selected: None,
|
||||
extra: None,
|
||||
option: None,
|
||||
updated: Some(help::get_now()),
|
||||
updated: Some(chrono::Local::now().timestamp() as usize),
|
||||
file_data: Some(tmpl::ITEM_SCRIPT.into()),
|
||||
})
|
||||
}
|
||||
@ -353,7 +355,7 @@ impl PrfItem {
|
||||
}
|
||||
|
||||
let file = self.file.clone().unwrap();
|
||||
let path = dirs::app_profiles_dir().join(file);
|
||||
let path = dirs::app_profiles_dir()?.join(file);
|
||||
fs::read_to_string(path).context("failed to read the file")
|
||||
}
|
||||
|
||||
@ -364,7 +366,7 @@ impl PrfItem {
|
||||
}
|
||||
|
||||
let file = self.file.clone().unwrap();
|
||||
let path = dirs::app_profiles_dir().join(file);
|
||||
let path = dirs::app_profiles_dir()?.join(file);
|
||||
fs::write(path, data.as_bytes()).context("failed to save the file")
|
||||
}
|
||||
|
||||
@ -373,7 +375,7 @@ impl PrfItem {
|
||||
let itype = self.itype.as_ref()?.as_str();
|
||||
let file = self.file.clone()?;
|
||||
let uid = self.uid.clone().unwrap_or("".into());
|
||||
let path = dirs::app_profiles_dir().join(file);
|
||||
let path = dirs::app_profiles_dir().ok()?.join(file);
|
||||
|
||||
if !path.exists() {
|
||||
return None;
|
||||
@ -382,11 +384,11 @@ impl PrfItem {
|
||||
match itype {
|
||||
"script" => Some(ChainItem {
|
||||
uid,
|
||||
data: ChainType::Script(fs::read_to_string(path).unwrap_or("".into())),
|
||||
data: ChainType::Script(fs::read_to_string(path).ok()?),
|
||||
}),
|
||||
"merge" => Some(ChainItem {
|
||||
uid,
|
||||
data: ChainType::Merge(config::read_merge_mapping(path)),
|
||||
data: ChainType::Merge(help::read_merge_mapping(&path).ok()?),
|
||||
}),
|
||||
_ => None,
|
||||
}
|
@ -1,30 +1,24 @@
|
||||
use super::prfitem::PrfItem;
|
||||
use super::ChainItem;
|
||||
use crate::utils::{config, dirs, help};
|
||||
use super::{prfitem::PrfItem, ChainItem};
|
||||
use crate::utils::{dirs, help};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_yaml::Mapping;
|
||||
use std::collections::HashMap;
|
||||
use std::{fs, io::Write};
|
||||
|
||||
///
|
||||
/// ## Profiles Config
|
||||
///
|
||||
/// Define the `profiles.yaml` schema
|
||||
///
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Profiles {
|
||||
pub struct IProfiles {
|
||||
/// same as PrfConfig.current
|
||||
current: Option<String>,
|
||||
pub current: Option<String>,
|
||||
|
||||
/// same as PrfConfig.chain
|
||||
chain: Option<Vec<String>>,
|
||||
pub chain: Option<Vec<String>>,
|
||||
|
||||
/// record valid fields for clash
|
||||
valid: Option<Vec<String>>,
|
||||
pub valid: Option<Vec<String>>,
|
||||
|
||||
/// profile list
|
||||
items: Option<Vec<PrfItem>>,
|
||||
pub items: Option<Vec<PrfItem>>,
|
||||
}
|
||||
|
||||
macro_rules! patch {
|
||||
@ -35,20 +29,14 @@ macro_rules! patch {
|
||||
};
|
||||
}
|
||||
|
||||
impl Profiles {
|
||||
impl IProfiles {
|
||||
pub fn new() -> Self {
|
||||
Profiles::read_file()
|
||||
}
|
||||
|
||||
/// read the config from the file
|
||||
pub fn read_file() -> Self {
|
||||
let mut profiles = config::read_yaml::<Self>(dirs::profiles_path());
|
||||
|
||||
match dirs::profiles_path().and_then(|path| help::read_yaml::<Self>(&path)) {
|
||||
Ok(mut profiles) => {
|
||||
if profiles.items.is_none() {
|
||||
profiles.items = Some(vec![]);
|
||||
}
|
||||
|
||||
// compatiable with the old old old version
|
||||
// compatible with the old old old version
|
||||
profiles.items.as_mut().map(|items| {
|
||||
for mut item in items.iter_mut() {
|
||||
if item.uid.is_none() {
|
||||
@ -56,51 +44,59 @@ impl Profiles {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
profiles
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!(target: "app", "{err}");
|
||||
Self::template()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn template() -> Self {
|
||||
Self {
|
||||
valid: Some(vec!["dns".into()]),
|
||||
items: Some(vec![]),
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// save the config to the file
|
||||
pub fn save_file(&self) -> Result<()> {
|
||||
config::save_yaml(
|
||||
dirs::profiles_path(),
|
||||
help::save_yaml(
|
||||
&dirs::profiles_path()?,
|
||||
self,
|
||||
Some("# Profiles Config for Clash Verge\n\n"),
|
||||
Some("# Profiles Config for Clash Verge"),
|
||||
)
|
||||
}
|
||||
|
||||
/// get the current uid
|
||||
pub fn get_current(&self) -> Option<String> {
|
||||
self.current.clone()
|
||||
}
|
||||
|
||||
/// only change the main to the target id
|
||||
pub fn put_current(&mut self, uid: String) -> Result<()> {
|
||||
/// 只修改current,valid和chain
|
||||
pub fn patch_config(&mut self, patch: IProfiles) -> Result<()> {
|
||||
if self.items.is_none() {
|
||||
self.items = Some(vec![]);
|
||||
}
|
||||
|
||||
if let Some(current) = patch.current {
|
||||
let items = self.items.as_ref().unwrap();
|
||||
let some_uid = Some(uid.clone());
|
||||
let some_uid = Some(current);
|
||||
|
||||
if items.iter().find(|&each| each.uid == some_uid).is_some() {
|
||||
if items.iter().any(|e| e.uid == some_uid) {
|
||||
self.current = some_uid;
|
||||
return self.save_file();
|
||||
}
|
||||
}
|
||||
|
||||
bail!("invalid uid \"{uid}\"");
|
||||
if let Some(chain) = patch.chain {
|
||||
self.chain = Some(chain);
|
||||
}
|
||||
|
||||
/// just change the `chain`
|
||||
pub fn put_chain(&mut self, chain: Option<Vec<String>>) -> Result<()> {
|
||||
self.chain = chain;
|
||||
self.save_file()
|
||||
if let Some(valid) = patch.valid {
|
||||
self.valid = Some(valid);
|
||||
}
|
||||
|
||||
/// just change the `field`
|
||||
pub fn put_valid(&mut self, valid: Option<Vec<String>>) -> Result<()> {
|
||||
self.valid = valid;
|
||||
self.save_file()
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_current(&self) -> Option<String> {
|
||||
self.current.clone()
|
||||
}
|
||||
|
||||
/// get items ref
|
||||
@ -110,8 +106,7 @@ impl Profiles {
|
||||
|
||||
/// find the item by the uid
|
||||
pub fn get_item(&self, uid: &String) -> Result<&PrfItem> {
|
||||
if self.items.is_some() {
|
||||
let items = self.items.as_ref().unwrap();
|
||||
if let Some(items) = self.items.as_ref() {
|
||||
let some_uid = Some(uid.clone());
|
||||
|
||||
for each in items.iter() {
|
||||
@ -140,7 +135,7 @@ impl Profiles {
|
||||
}
|
||||
|
||||
let file = item.file.clone().unwrap();
|
||||
let path = dirs::app_profiles_dir().join(&file);
|
||||
let path = dirs::app_profiles_dir()?.join(&file);
|
||||
|
||||
fs::File::create(path)
|
||||
.context(format!("failed to create file \"{}\"", file))?
|
||||
@ -209,7 +204,7 @@ impl Profiles {
|
||||
// the file must exists
|
||||
each.file = Some(file.clone());
|
||||
|
||||
let path = dirs::app_profiles_dir().join(&file);
|
||||
let path = dirs::app_profiles_dir()?.join(&file);
|
||||
|
||||
fs::File::create(path)
|
||||
.context(format!("failed to create file \"{}\"", file))?
|
||||
@ -244,11 +239,13 @@ impl Profiles {
|
||||
|
||||
if let Some(index) = index {
|
||||
items.remove(index).file.map(|file| {
|
||||
let path = dirs::app_profiles_dir().join(file);
|
||||
let _ = dirs::app_profiles_dir().map(|path| {
|
||||
let path = path.join(file);
|
||||
if path.exists() {
|
||||
let _ = fs::remove_file(path);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// delete the original uid
|
||||
@ -272,22 +269,18 @@ impl Profiles {
|
||||
return Ok(config);
|
||||
}
|
||||
|
||||
let current = self.current.clone().unwrap();
|
||||
let current = self.current.as_ref().unwrap();
|
||||
for item in self.items.as_ref().unwrap().iter() {
|
||||
if item.uid == Some(current.clone()) {
|
||||
let file_path = match item.file.clone() {
|
||||
Some(file) => dirs::app_profiles_dir().join(file),
|
||||
if item.uid.as_ref() == Some(current) {
|
||||
let file_path = match item.file.as_ref() {
|
||||
Some(file) => dirs::app_profiles_dir()?.join(file),
|
||||
None => bail!("failed to get the file field"),
|
||||
};
|
||||
|
||||
if !file_path.exists() {
|
||||
bail!("failed to read the file \"{}\"", file_path.display());
|
||||
}
|
||||
|
||||
return Ok(config::read_merge_mapping(file_path.clone()));
|
||||
return Ok(help::read_merge_mapping(&file_path)?);
|
||||
}
|
||||
}
|
||||
bail!("failed to find current profile \"uid:{current}\"");
|
||||
bail!("failed to find the current profile \"uid:{current}\"");
|
||||
}
|
||||
|
||||
/// generate the data for activate clash config
|
||||
@ -317,13 +310,3 @@ pub struct PrfActivate {
|
||||
pub chain: Vec<ChainItem>,
|
||||
pub valid: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct RuntimeResult {
|
||||
pub config: Option<Mapping>,
|
||||
pub config_yaml: Option<String>,
|
||||
// 记录在配置中(包括merge和script生成的)出现过的keys
|
||||
// 这些keys不一定都生效
|
||||
pub exists_keys: Vec<String>,
|
||||
pub chain_logs: HashMap<String, Vec<(String, String)>>,
|
||||
}
|
18
src-tauri/src/config/runtime.rs
Normal file
18
src-tauri/src/config/runtime.rs
Normal file
@ -0,0 +1,18 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_yaml::Mapping;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct IRuntime {
|
||||
pub config: Option<Mapping>,
|
||||
// 记录在配置中(包括merge和script生成的)出现过的keys
|
||||
// 这些keys不一定都生效
|
||||
pub exists_keys: Vec<String>,
|
||||
pub chain_logs: HashMap<String, Vec<(String, String)>>,
|
||||
}
|
||||
|
||||
impl IRuntime {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
@ -1,12 +1,11 @@
|
||||
use crate::utils::{config, dirs};
|
||||
use crate::utils::{dirs, help};
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// ### `verge.yaml` schema
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Verge {
|
||||
/// app listening port
|
||||
/// for app singleton
|
||||
pub struct IVerge {
|
||||
/// app listening port for app singleton
|
||||
pub app_singleton_port: Option<u16>,
|
||||
|
||||
// i18n
|
||||
@ -48,7 +47,7 @@ pub struct Verge {
|
||||
pub proxy_guard_duration: Option<u64>,
|
||||
|
||||
/// theme setting
|
||||
pub theme_setting: Option<VergeTheme>,
|
||||
pub theme_setting: Option<IVergeTheme>,
|
||||
|
||||
/// web ui list
|
||||
pub web_ui_list: Option<Vec<String>>,
|
||||
@ -69,7 +68,7 @@ pub struct Verge {
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct VergeTheme {
|
||||
pub struct IVergeTheme {
|
||||
pub primary_color: Option<String>,
|
||||
pub secondary_color: Option<String>,
|
||||
pub primary_text: Option<String>,
|
||||
@ -84,23 +83,42 @@ pub struct VergeTheme {
|
||||
pub css_injection: Option<String>,
|
||||
}
|
||||
|
||||
impl Verge {
|
||||
impl IVerge {
|
||||
pub fn new() -> Self {
|
||||
config::read_yaml::<Verge>(dirs::verge_path())
|
||||
match dirs::verge_path().and_then(|path| help::read_yaml::<IVerge>(&path)) {
|
||||
Ok(config) => config,
|
||||
Err(err) => {
|
||||
log::error!(target: "app", "{err}");
|
||||
Self::template()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Save Verge App Config
|
||||
pub fn template() -> Self {
|
||||
Self {
|
||||
clash_core: Some("clash".into()),
|
||||
language: Some("en".into()),
|
||||
theme_mode: Some("system".into()),
|
||||
theme_blur: Some(false),
|
||||
traffic_graph: Some(true),
|
||||
enable_auto_launch: Some(false),
|
||||
enable_silent_start: Some(false),
|
||||
enable_system_proxy: Some(false),
|
||||
enable_proxy_guard: Some(false),
|
||||
proxy_guard_duration: Some(30),
|
||||
auto_close_connection: Some(true),
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Save IVerge App Config
|
||||
pub fn save_file(&self) -> Result<()> {
|
||||
config::save_yaml(
|
||||
dirs::verge_path(),
|
||||
self,
|
||||
Some("# The Config for Clash Verge App\n\n"),
|
||||
)
|
||||
help::save_yaml(&dirs::verge_path()?, &self, Some("# Clash Verge Config"))
|
||||
}
|
||||
|
||||
/// patch verge config
|
||||
/// only save to file
|
||||
pub fn patch_config(&mut self, patch: Verge) -> Result<()> {
|
||||
pub fn patch_config(&mut self, patch: IVerge) {
|
||||
macro_rules! patch {
|
||||
($key: tt) => {
|
||||
if patch.$key.is_some() {
|
||||
@ -130,7 +148,18 @@ impl Verge {
|
||||
|
||||
patch!(auto_close_connection);
|
||||
patch!(default_latency_test);
|
||||
}
|
||||
|
||||
self.save_file()
|
||||
/// 在初始化前尝试拿到单例端口的值
|
||||
pub fn get_singleton_port() -> u16 {
|
||||
#[cfg(not(feature = "verge-dev"))]
|
||||
const SERVER_PORT: u16 = 33331;
|
||||
#[cfg(feature = "verge-dev")]
|
||||
const SERVER_PORT: u16 = 11233;
|
||||
|
||||
match dirs::verge_path().and_then(|path| help::read_yaml::<IVerge>(&path)) {
|
||||
Ok(config) => config.app_singleton_port.unwrap_or(SERVER_PORT),
|
||||
Err(_) => SERVER_PORT, // 这里就不log错误了
|
||||
}
|
||||
}
|
||||
}
|
64
src-tauri/src/core/clash_api.rs
Normal file
64
src-tauri/src/core/clash_api.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use crate::config::Config;
|
||||
use anyhow::{bail, Result};
|
||||
use reqwest::header::HeaderMap;
|
||||
use serde_yaml::Mapping;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// PUT /configs
|
||||
/// path 是绝对路径
|
||||
pub async fn put_configs(path: &str) -> Result<()> {
|
||||
let (url, headers) = clash_client_info()?;
|
||||
let url = format!("{url}/configs");
|
||||
|
||||
let mut data = HashMap::new();
|
||||
data.insert("path", path);
|
||||
|
||||
let client = reqwest::ClientBuilder::new().no_proxy().build()?;
|
||||
let builder = client.put(&url).headers(headers).json(&data);
|
||||
let response = builder.send().await?;
|
||||
|
||||
match response.status().as_u16() {
|
||||
204 => Ok(()),
|
||||
status @ _ => {
|
||||
bail!("failed to put configs with status \"{status}\"")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// PATCH /configs
|
||||
pub async fn patch_configs(config: &Mapping) -> Result<()> {
|
||||
let (url, headers) = clash_client_info()?;
|
||||
let url = format!("{url}/configs");
|
||||
|
||||
let client = reqwest::ClientBuilder::new().no_proxy().build()?;
|
||||
let builder = client.patch(&url).headers(headers.clone()).json(config);
|
||||
builder.send().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 根据clash info获取clash服务地址和请求头
|
||||
fn clash_client_info() -> Result<(String, HeaderMap)> {
|
||||
let info = { Config::clash().data().get_info()? };
|
||||
|
||||
if info.server.is_none() {
|
||||
let status = &info.status;
|
||||
if info.port.is_none() {
|
||||
bail!("failed to parse config.yaml file with status {status}");
|
||||
} else {
|
||||
bail!("failed to parse the server with status {status}");
|
||||
}
|
||||
}
|
||||
|
||||
let server = info.server.unwrap();
|
||||
let server = format!("http://{server}");
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("Content-Type", "application/json".parse()?);
|
||||
|
||||
if let Some(secret) = info.secret.as_ref() {
|
||||
let secret = format!("Bearer {}", secret.clone()).parse()?;
|
||||
headers.insert("Authorization", secret);
|
||||
}
|
||||
|
||||
Ok((server, headers))
|
||||
}
|
246
src-tauri/src/core/core.rs
Normal file
246
src-tauri/src/core/core.rs
Normal file
@ -0,0 +1,246 @@
|
||||
use super::{clash_api, logger::Logger};
|
||||
use crate::log_err;
|
||||
use crate::{config::*, utils::dirs};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use std::{fs, io::Write, sync::Arc, time::Duration};
|
||||
use sysinfo::{Pid, PidExt, ProcessExt, System, SystemExt};
|
||||
use tauri::api::process::{Command, CommandChild, CommandEvent};
|
||||
use tokio::time::sleep;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CoreManager {
|
||||
sidecar: Arc<Mutex<Option<CommandChild>>>,
|
||||
|
||||
#[allow(unused)]
|
||||
use_service_mode: Arc<Mutex<bool>>,
|
||||
}
|
||||
|
||||
impl CoreManager {
|
||||
pub fn global() -> &'static CoreManager {
|
||||
static CORE_MANAGER: OnceCell<CoreManager> = OnceCell::new();
|
||||
|
||||
CORE_MANAGER.get_or_init(|| CoreManager {
|
||||
sidecar: Arc::new(Mutex::new(None)),
|
||||
use_service_mode: Arc::new(Mutex::new(false)),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn init(&self) -> Result<()> {
|
||||
// kill old clash process
|
||||
let _ = dirs::clash_pid_path()
|
||||
.and_then(|path| fs::read(path).map(|p| p.to_vec()).context(""))
|
||||
.and_then(|pid| String::from_utf8_lossy(&pid).parse().context(""))
|
||||
.map(|pid| {
|
||||
let mut system = System::new();
|
||||
system.refresh_all();
|
||||
system.process(Pid::from_u32(pid)).map(|proc| {
|
||||
if proc.name().contains("clash") {
|
||||
proc.kill();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
tauri::async_runtime::spawn(async {
|
||||
// 启动clash
|
||||
log_err!(Self::global().run_core().await);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 检查配置是否正确
|
||||
pub fn check_config(&self) -> Result<()> {
|
||||
let config_path = Config::generate_file(ConfigType::Check)?;
|
||||
let config_path = dirs::path_to_str(&config_path)?;
|
||||
|
||||
let clash_core = { Config::verge().latest().clash_core.clone() };
|
||||
let clash_core = clash_core.unwrap_or("clash".into());
|
||||
|
||||
let output = Command::new_sidecar(clash_core)?
|
||||
.args(["-t", "-f", config_path])
|
||||
.output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
Logger::global().set_log(output.stdout.clone());
|
||||
bail!("{}", output.stdout); // 过滤掉终端颜色值
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 启动核心
|
||||
pub async fn run_core(&self) -> Result<()> {
|
||||
let config_path = Config::generate_file(ConfigType::Run)?;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use super::win_service;
|
||||
|
||||
// 服务模式
|
||||
let enable = {
|
||||
let enable = Config::verge().data().enable_service_mode.clone();
|
||||
enable.unwrap_or(false)
|
||||
};
|
||||
|
||||
*self.use_service_mode.lock() = enable;
|
||||
|
||||
if enable {
|
||||
// 服务模式启动失败就直接运行sidecar
|
||||
match {
|
||||
win_service::check_service().await?;
|
||||
win_service::run_core_by_service().await
|
||||
} {
|
||||
Ok(_) => return Ok(()),
|
||||
Err(err) => {
|
||||
// 修改这个值,免得stop出错
|
||||
*self.use_service_mode.lock() = false;
|
||||
|
||||
log::error!(target: "app", "{err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut sidecar = self.sidecar.lock();
|
||||
|
||||
if let Some(child) = sidecar.take() {
|
||||
let _ = child.kill();
|
||||
}
|
||||
|
||||
let app_dir = dirs::app_home_dir()?;
|
||||
let app_dir = dirs::path_to_str(&app_dir)?;
|
||||
|
||||
let clash_core = { Config::verge().latest().clash_core.clone() };
|
||||
let clash_core = clash_core.unwrap_or("clash".into());
|
||||
|
||||
let config_path = dirs::path_to_str(&config_path)?;
|
||||
|
||||
// fix #212
|
||||
let args = match clash_core.as_str() {
|
||||
"clash-meta" => vec!["-m", "-d", app_dir, "-f", config_path],
|
||||
_ => vec!["-d", app_dir, "-f", config_path],
|
||||
};
|
||||
|
||||
let cmd = Command::new_sidecar(clash_core)?;
|
||||
let (mut rx, cmd_child) = cmd.args(args).spawn()?;
|
||||
|
||||
// 将pid写入文件中
|
||||
crate::log_err!({
|
||||
let pid = cmd_child.pid();
|
||||
let path = dirs::clash_pid_path()?;
|
||||
fs::File::create(path)
|
||||
.context("failed to create the pid file")?
|
||||
.write(format!("{pid}").as_bytes())
|
||||
.context("failed to write pid to the file")?;
|
||||
<Result<()>>::Ok(())
|
||||
});
|
||||
|
||||
*sidecar = Some(cmd_child);
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
while let Some(event) = rx.recv().await {
|
||||
match event {
|
||||
CommandEvent::Stdout(line) => {
|
||||
let can_short = line.starts_with("time=") && line.len() > 33;
|
||||
let stdout = if can_short { &line[33..] } else { &line };
|
||||
log::info!(target: "app" ,"[clash]: {}", stdout);
|
||||
Logger::global().set_log(line);
|
||||
}
|
||||
CommandEvent::Stderr(err) => {
|
||||
log::error!(target: "app" ,"[clash]: {err}");
|
||||
Logger::global().set_log(err);
|
||||
}
|
||||
CommandEvent::Error(err) => {
|
||||
log::error!(target: "app" ,"[clash]: {err}");
|
||||
Logger::global().set_log(err);
|
||||
}
|
||||
CommandEvent::Terminated(_) => {
|
||||
log::info!(target: "app" ,"clash core terminated");
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 停止核心运行
|
||||
pub fn stop_core(&self) -> Result<()> {
|
||||
#[cfg(target_os = "windows")]
|
||||
if *self.use_service_mode.lock() {
|
||||
tauri::async_runtime::block_on(async move {
|
||||
log_err!(super::win_service::stop_core_by_service().await);
|
||||
});
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut sidecar = self.sidecar.lock();
|
||||
if let Some(child) = sidecar.take() {
|
||||
let _ = child.kill();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 切换核心
|
||||
pub async fn change_core(&self, clash_core: Option<String>) -> Result<()> {
|
||||
let clash_core = clash_core.ok_or(anyhow::anyhow!("clash core is null"))?;
|
||||
|
||||
if &clash_core != "clash" && &clash_core != "clash-meta" {
|
||||
bail!("invalid clash core name \"{clash_core}\"");
|
||||
}
|
||||
|
||||
Config::verge().draft().clash_core = Some(clash_core);
|
||||
|
||||
// 清掉旧日志
|
||||
Logger::global().clear_log();
|
||||
|
||||
match self.run_core().await {
|
||||
Ok(_) => {
|
||||
Config::verge().apply();
|
||||
Config::runtime().apply();
|
||||
log_err!(Config::verge().latest().save_file());
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
Config::verge().discard();
|
||||
Config::runtime().discard();
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 更新proxies那些
|
||||
/// 如果涉及端口和外部控制则需要重启
|
||||
pub async fn update_config(&self) -> Result<()> {
|
||||
// 更新配置
|
||||
Config::generate()?;
|
||||
|
||||
// 检查配置是否正常
|
||||
self.check_config()?;
|
||||
|
||||
// 更新运行时配置
|
||||
let path = Config::generate_file(ConfigType::Run)?;
|
||||
let path = dirs::path_to_str(&path)?;
|
||||
|
||||
// 发送请求 发送5次
|
||||
for i in 0..5 {
|
||||
match clash_api::put_configs(path).await {
|
||||
Ok(_) => break,
|
||||
Err(err) => {
|
||||
if i < 4 {
|
||||
log::error!(target: "app", "{err}");
|
||||
} else {
|
||||
bail!(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
sleep(Duration::from_millis(250)).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,65 +1,77 @@
|
||||
use super::tray::Tray;
|
||||
use crate::log_if_err;
|
||||
use crate::log_err;
|
||||
use anyhow::{bail, Result};
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
use tauri::{AppHandle, Manager, Window};
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Handle {
|
||||
pub app_handle: Option<AppHandle>,
|
||||
pub app_handle: Arc<Mutex<Option<AppHandle>>>,
|
||||
}
|
||||
|
||||
impl Handle {
|
||||
pub fn set_inner(&mut self, app_handle: AppHandle) {
|
||||
self.app_handle = Some(app_handle);
|
||||
pub fn global() -> &'static Handle {
|
||||
static HANDLE: OnceCell<Handle> = OnceCell::new();
|
||||
|
||||
HANDLE.get_or_init(|| Handle {
|
||||
app_handle: Arc::new(Mutex::new(None)),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn init(&self, app_handle: AppHandle) {
|
||||
*self.app_handle.lock() = Some(app_handle);
|
||||
}
|
||||
|
||||
pub fn get_window(&self) -> Option<Window> {
|
||||
self.app_handle
|
||||
.lock()
|
||||
.as_ref()
|
||||
.map_or(None, |a| a.get_window("main"))
|
||||
}
|
||||
|
||||
pub fn refresh_clash(&self) {
|
||||
if let Some(window) = self.get_window() {
|
||||
log_if_err!(window.emit("verge://refresh-clash-config", "yes"));
|
||||
pub fn refresh_clash() {
|
||||
if let Some(window) = Self::global().get_window() {
|
||||
log_err!(window.emit("verge://refresh-clash-config", "yes"));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh_verge(&self) {
|
||||
if let Some(window) = self.get_window() {
|
||||
log_if_err!(window.emit("verge://refresh-verge-config", "yes"));
|
||||
pub fn refresh_verge() {
|
||||
if let Some(window) = Self::global().get_window() {
|
||||
log_err!(window.emit("verge://refresh-verge-config", "yes"));
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn refresh_profiles(&self) {
|
||||
if let Some(window) = self.get_window() {
|
||||
log_if_err!(window.emit("verge://refresh-profiles-config", "yes"));
|
||||
pub fn refresh_profiles() {
|
||||
if let Some(window) = Self::global().get_window() {
|
||||
log_err!(window.emit("verge://refresh-profiles-config", "yes"));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notice_message(&self, status: String, msg: String) {
|
||||
if let Some(window) = self.get_window() {
|
||||
log_if_err!(window.emit("verge://notice-message", (status, msg)));
|
||||
pub fn notice_message<S: Into<String>, M: Into<String>>(status: S, msg: M) {
|
||||
if let Some(window) = Self::global().get_window() {
|
||||
log_err!(window.emit("verge://notice-message", (status.into(), msg.into())));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_systray(&self) -> Result<()> {
|
||||
if self.app_handle.is_none() {
|
||||
bail!("update_systray unhandle error");
|
||||
pub fn update_systray() -> Result<()> {
|
||||
let app_handle = Self::global().app_handle.lock();
|
||||
if app_handle.is_none() {
|
||||
bail!("update_systray unhandled error");
|
||||
}
|
||||
let app_handle = self.app_handle.as_ref().unwrap();
|
||||
Tray::update_systray(app_handle)?;
|
||||
Tray::update_systray(app_handle.as_ref().unwrap())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// update the system tray state
|
||||
pub fn update_systray_part(&self) -> Result<()> {
|
||||
if self.app_handle.is_none() {
|
||||
bail!("update_systray unhandle error");
|
||||
pub fn update_systray_part() -> Result<()> {
|
||||
let app_handle = Self::global().app_handle.lock();
|
||||
if app_handle.is_none() {
|
||||
bail!("update_systray unhandled error");
|
||||
}
|
||||
let app_handle = self.app_handle.as_ref().unwrap();
|
||||
Tray::update_part(app_handle)?;
|
||||
Tray::update_part(app_handle.as_ref().unwrap())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -1,52 +1,58 @@
|
||||
use crate::{data::*, feat, log_if_err};
|
||||
use crate::{config::Config, feat, log_err};
|
||||
use anyhow::{bail, Result};
|
||||
use std::collections::HashMap;
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tauri::{AppHandle, GlobalShortcutManager};
|
||||
|
||||
pub struct Hotkey {
|
||||
current: Vec<String>, // 保存当前的热键设置
|
||||
manager: Option<AppHandle>,
|
||||
current: Arc<Mutex<Vec<String>>>, // 保存当前的热键设置
|
||||
|
||||
app_handle: Arc<Mutex<Option<AppHandle>>>,
|
||||
}
|
||||
|
||||
impl Hotkey {
|
||||
pub fn new() -> Hotkey {
|
||||
Hotkey {
|
||||
current: Vec::new(),
|
||||
manager: None,
|
||||
}
|
||||
pub fn global() -> &'static Hotkey {
|
||||
static HOTKEY: OnceCell<Hotkey> = OnceCell::new();
|
||||
|
||||
HOTKEY.get_or_init(|| Hotkey {
|
||||
current: Arc::new(Mutex::new(Vec::new())),
|
||||
app_handle: Arc::new(Mutex::new(None)),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn init(&mut self, app_handle: AppHandle) -> Result<()> {
|
||||
self.manager = Some(app_handle);
|
||||
let data = Data::global();
|
||||
let verge = data.verge.lock();
|
||||
pub fn init(&self, app_handle: AppHandle) -> Result<()> {
|
||||
*self.app_handle.lock() = Some(app_handle);
|
||||
|
||||
if let Some(hotkeys) = verge.hotkeys.as_ref() {
|
||||
let verge = Config::verge();
|
||||
|
||||
if let Some(hotkeys) = verge.latest().hotkeys.as_ref() {
|
||||
for hotkey in hotkeys.iter() {
|
||||
let mut iter = hotkey.split(',');
|
||||
let func = iter.next();
|
||||
let key = iter.next();
|
||||
|
||||
if func.is_some() && key.is_some() {
|
||||
log_if_err!(self.register(key.unwrap(), func.unwrap()));
|
||||
log_err!(self.register(key.unwrap(), func.unwrap()));
|
||||
} else {
|
||||
log::error!(target: "app", "invalid hotkey \"{}\":\"{}\"", key.unwrap_or("None"), func.unwrap_or("None"));
|
||||
}
|
||||
}
|
||||
self.current = hotkeys.clone();
|
||||
*self.current.lock() = hotkeys.clone();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_manager(&self) -> Result<impl GlobalShortcutManager> {
|
||||
if self.manager.is_none() {
|
||||
let app_handle = self.app_handle.lock();
|
||||
if app_handle.is_none() {
|
||||
bail!("failed to get hotkey manager");
|
||||
}
|
||||
Ok(self.manager.as_ref().unwrap().global_shortcut_manager())
|
||||
Ok(app_handle.as_ref().unwrap().global_shortcut_manager())
|
||||
}
|
||||
|
||||
fn register(&mut self, hotkey: &str, func: &str) -> Result<()> {
|
||||
fn register(&self, hotkey: &str, func: &str) -> Result<()> {
|
||||
let mut manager = self.get_manager()?;
|
||||
|
||||
if manager.is_registered(hotkey)? {
|
||||
@ -54,10 +60,10 @@ impl Hotkey {
|
||||
}
|
||||
|
||||
let f = match func.trim() {
|
||||
"clash_mode_rule" => || feat::change_clash_mode("rule"),
|
||||
"clash_mode_global" => || feat::change_clash_mode("global"),
|
||||
"clash_mode_direct" => || feat::change_clash_mode("direct"),
|
||||
"clash_mode_script" => || feat::change_clash_mode("script"),
|
||||
"clash_mode_rule" => || feat::change_clash_mode("rule".into()),
|
||||
"clash_mode_global" => || feat::change_clash_mode("global".into()),
|
||||
"clash_mode_direct" => || feat::change_clash_mode("direct".into()),
|
||||
"clash_mode_script" => || feat::change_clash_mode("script".into()),
|
||||
"toggle_system_proxy" => || feat::toggle_system_proxy(),
|
||||
"enable_system_proxy" => || feat::enable_system_proxy(),
|
||||
"disable_system_proxy" => || feat::disable_system_proxy(),
|
||||
@ -73,14 +79,14 @@ impl Hotkey {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unregister(&mut self, hotkey: &str) -> Result<()> {
|
||||
fn unregister(&self, hotkey: &str) -> Result<()> {
|
||||
self.get_manager()?.unregister(&hotkey)?;
|
||||
log::info!(target: "app", "unregister hotkey {hotkey}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update(&mut self, new_hotkeys: Vec<String>) -> Result<()> {
|
||||
let current = self.current.to_owned();
|
||||
pub fn update(&self, new_hotkeys: Vec<String>) -> Result<()> {
|
||||
let mut current = self.current.lock();
|
||||
let old_map = Self::get_map_from_vec(¤t);
|
||||
let new_map = Self::get_map_from_vec(&new_hotkeys);
|
||||
|
||||
@ -91,10 +97,10 @@ impl Hotkey {
|
||||
});
|
||||
|
||||
add.iter().for_each(|(key, func)| {
|
||||
log_if_err!(self.register(key, func));
|
||||
log_err!(self.register(key, func));
|
||||
});
|
||||
|
||||
self.current = new_hotkeys;
|
||||
*current = new_hotkeys;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
36
src-tauri/src/core/logger.rs
Normal file
36
src-tauri/src/core/logger.rs
Normal file
@ -0,0 +1,36 @@
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use std::{collections::VecDeque, sync::Arc};
|
||||
|
||||
const LOGS_QUEUE_LEN: usize = 100;
|
||||
|
||||
pub struct Logger {
|
||||
log_data: Arc<Mutex<VecDeque<String>>>,
|
||||
}
|
||||
|
||||
impl Logger {
|
||||
pub fn global() -> &'static Logger {
|
||||
static LOGGER: OnceCell<Logger> = OnceCell::new();
|
||||
|
||||
LOGGER.get_or_init(|| Logger {
|
||||
log_data: Arc::new(Mutex::new(VecDeque::with_capacity(LOGS_QUEUE_LEN + 10))),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_log(&self) -> VecDeque<String> {
|
||||
self.log_data.lock().clone()
|
||||
}
|
||||
|
||||
pub fn set_log(&self, text: String) {
|
||||
let mut logs = self.log_data.lock();
|
||||
if logs.len() > LOGS_QUEUE_LEN {
|
||||
logs.pop_front();
|
||||
}
|
||||
logs.push_back(text);
|
||||
}
|
||||
|
||||
pub fn clear_log(&self) {
|
||||
let mut logs = self.log_data.lock();
|
||||
logs.clear();
|
||||
}
|
||||
}
|
@ -1,349 +1,11 @@
|
||||
use self::handle::Handle;
|
||||
use self::hotkey::Hotkey;
|
||||
use self::sysopt::Sysopt;
|
||||
use self::timer::Timer;
|
||||
use crate::config::enhance_config;
|
||||
use crate::data::*;
|
||||
use crate::log_if_err;
|
||||
use anyhow::{bail, Result};
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use serde_yaml::{Mapping, Value};
|
||||
use std::sync::Arc;
|
||||
|
||||
mod handle;
|
||||
mod hotkey;
|
||||
mod service;
|
||||
mod sysopt;
|
||||
mod timer;
|
||||
pub mod clash_api;
|
||||
mod core;
|
||||
pub mod handle;
|
||||
pub mod hotkey;
|
||||
pub mod logger;
|
||||
pub mod sysopt;
|
||||
pub mod timer;
|
||||
pub mod tray;
|
||||
pub mod win_service;
|
||||
|
||||
pub use self::service::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Core {
|
||||
pub service: Arc<Mutex<Service>>,
|
||||
pub sysopt: Arc<Mutex<Sysopt>>,
|
||||
pub timer: Arc<Mutex<Timer>>,
|
||||
pub hotkey: Arc<Mutex<Hotkey>>,
|
||||
pub runtime: Arc<Mutex<RuntimeResult>>,
|
||||
pub handle: Arc<Mutex<Handle>>,
|
||||
}
|
||||
|
||||
impl Core {
|
||||
pub fn global() -> &'static Core {
|
||||
static CORE: OnceCell<Core> = OnceCell::new();
|
||||
|
||||
CORE.get_or_init(|| Core {
|
||||
service: Arc::new(Mutex::new(Service::new())),
|
||||
sysopt: Arc::new(Mutex::new(Sysopt::new())),
|
||||
timer: Arc::new(Mutex::new(Timer::new())),
|
||||
hotkey: Arc::new(Mutex::new(Hotkey::new())),
|
||||
runtime: Arc::new(Mutex::new(RuntimeResult::default())),
|
||||
handle: Arc::new(Mutex::new(Handle::default())),
|
||||
})
|
||||
}
|
||||
|
||||
/// initialize the core state
|
||||
pub fn init(&self, app_handle: tauri::AppHandle) {
|
||||
// kill old clash process
|
||||
Service::kill_old_clash();
|
||||
|
||||
let mut handle = self.handle.lock();
|
||||
handle.set_inner(app_handle.clone());
|
||||
drop(handle);
|
||||
|
||||
let mut service = self.service.lock();
|
||||
log_if_err!(service.start());
|
||||
drop(service);
|
||||
|
||||
log_if_err!(self.activate());
|
||||
|
||||
let mut sysopt = self.sysopt.lock();
|
||||
log_if_err!(sysopt.init_launch());
|
||||
log_if_err!(sysopt.init_sysproxy());
|
||||
drop(sysopt);
|
||||
|
||||
let handle = self.handle.lock();
|
||||
log_if_err!(handle.update_systray_part());
|
||||
drop(handle);
|
||||
|
||||
let mut hotkey = self.hotkey.lock();
|
||||
log_if_err!(hotkey.init(app_handle));
|
||||
drop(hotkey);
|
||||
|
||||
// timer initialize
|
||||
let mut timer = self.timer.lock();
|
||||
log_if_err!(timer.restore());
|
||||
}
|
||||
|
||||
/// restart the clash sidecar
|
||||
pub fn restart_clash(&self) -> Result<()> {
|
||||
let mut service = self.service.lock();
|
||||
service.restart()?;
|
||||
drop(service);
|
||||
self.activate()
|
||||
}
|
||||
|
||||
/// change the clash core
|
||||
pub fn change_core(&self, clash_core: Option<String>) -> Result<()> {
|
||||
let clash_core = clash_core.unwrap_or("clash".into());
|
||||
|
||||
if &clash_core != "clash" && &clash_core != "clash-meta" {
|
||||
bail!("invalid clash core name \"{clash_core}\"");
|
||||
}
|
||||
|
||||
let global = Data::global();
|
||||
let mut verge = global.verge.lock();
|
||||
verge.patch_config(Verge {
|
||||
clash_core: Some(clash_core.clone()),
|
||||
..Verge::default()
|
||||
})?;
|
||||
drop(verge);
|
||||
|
||||
let mut service = self.service.lock();
|
||||
service.clear_logs();
|
||||
service.restart()?;
|
||||
drop(service);
|
||||
|
||||
self.activate()
|
||||
}
|
||||
|
||||
/// Patch Clash
|
||||
/// handle the clash config changed
|
||||
pub fn patch_clash(&self, patch: Mapping) -> Result<()> {
|
||||
let patch_cloned = patch.clone();
|
||||
let clash_mode = patch.get("mode");
|
||||
let mixed_port = patch.get("mixed-port");
|
||||
let external = patch.get("external-controller");
|
||||
let secret = patch.get("secret");
|
||||
|
||||
let valid_port = {
|
||||
let global = Data::global();
|
||||
let mut clash = global.clash.lock();
|
||||
clash.patch_config(patch_cloned)?;
|
||||
clash.info.port.is_some()
|
||||
};
|
||||
|
||||
// todo: port check
|
||||
if (mixed_port.is_some() && valid_port) || external.is_some() || secret.is_some() {
|
||||
let mut service = self.service.lock();
|
||||
service.restart()?;
|
||||
drop(service);
|
||||
|
||||
self.activate()?;
|
||||
|
||||
let mut sysopt = self.sysopt.lock();
|
||||
sysopt.init_sysproxy()?;
|
||||
}
|
||||
|
||||
if clash_mode.is_some() {
|
||||
let handle = self.handle.lock();
|
||||
handle.update_systray_part()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Patch Verge
|
||||
pub fn patch_verge(&self, patch: Verge) -> Result<()> {
|
||||
// save the patch
|
||||
let global = Data::global();
|
||||
let mut verge = global.verge.lock();
|
||||
verge.patch_config(patch.clone())?;
|
||||
drop(verge);
|
||||
|
||||
let tun_mode = patch.enable_tun_mode;
|
||||
let auto_launch = patch.enable_auto_launch;
|
||||
let system_proxy = patch.enable_system_proxy;
|
||||
let proxy_bypass = patch.system_proxy_bypass;
|
||||
let proxy_guard = patch.enable_proxy_guard;
|
||||
let language = patch.language;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let service_mode = patch.enable_service_mode;
|
||||
|
||||
// 重启服务
|
||||
if service_mode.is_some() {
|
||||
let mut service = self.service.lock();
|
||||
service.restart()?;
|
||||
drop(service);
|
||||
}
|
||||
|
||||
if tun_mode.is_some() && *tun_mode.as_ref().unwrap_or(&false) {
|
||||
let wintun_dll = crate::utils::dirs::app_home_dir().join("wintun.dll");
|
||||
if !wintun_dll.exists() {
|
||||
bail!("failed to enable TUN for missing `wintun.dll`");
|
||||
}
|
||||
}
|
||||
|
||||
if service_mode.is_some() || tun_mode.is_some() {
|
||||
self.activate()?;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
if tun_mode.is_some() {
|
||||
self.activate()?;
|
||||
}
|
||||
|
||||
let mut sysopt = self.sysopt.lock();
|
||||
|
||||
if auto_launch.is_some() {
|
||||
sysopt.update_launch()?;
|
||||
}
|
||||
if system_proxy.is_some() || proxy_bypass.is_some() {
|
||||
sysopt.update_sysproxy()?;
|
||||
sysopt.guard_proxy();
|
||||
}
|
||||
if proxy_guard.unwrap_or(false) {
|
||||
sysopt.guard_proxy();
|
||||
}
|
||||
|
||||
// 更新tray
|
||||
if language.is_some() {
|
||||
let handle = self.handle.lock();
|
||||
handle.update_systray()?;
|
||||
} else if system_proxy.is_some() || tun_mode.is_some() {
|
||||
let handle = self.handle.lock();
|
||||
handle.update_systray_part()?;
|
||||
}
|
||||
|
||||
if patch.hotkeys.is_some() {
|
||||
let mut hotkey = self.hotkey.lock();
|
||||
hotkey.update(patch.hotkeys.unwrap())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// update rule/global/direct/script mode
|
||||
pub fn update_mode(&self, mode: &str) -> Result<()> {
|
||||
// save config to file
|
||||
let info = {
|
||||
let global = Data::global();
|
||||
let mut clash = global.clash.lock();
|
||||
clash.config.insert(Value::from("mode"), Value::from(mode));
|
||||
clash.save_config()?;
|
||||
clash.info.clone()
|
||||
};
|
||||
|
||||
let mut mapping = Mapping::new();
|
||||
mapping.insert(Value::from("mode"), Value::from(mode));
|
||||
|
||||
let handle = self.handle.clone();
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
log_if_err!(Service::patch_config(info, mapping.to_owned()).await);
|
||||
|
||||
// update tray
|
||||
let handle = handle.lock();
|
||||
handle.refresh_clash();
|
||||
log_if_err!(handle.update_systray_part());
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// activate the profile
|
||||
/// auto activate enhanced profile
|
||||
/// 触发clash配置更新
|
||||
pub fn activate(&self) -> Result<()> {
|
||||
let global = Data::global();
|
||||
|
||||
let verge = global.verge.lock();
|
||||
let clash = global.clash.lock();
|
||||
let profiles = global.profiles.lock();
|
||||
|
||||
let tun_mode = verge.enable_tun_mode.clone().unwrap_or(false);
|
||||
let profile_activate = profiles.gen_activate()?;
|
||||
|
||||
let clash_config = clash.config.clone();
|
||||
let clash_info = clash.info.clone();
|
||||
|
||||
drop(clash);
|
||||
drop(verge);
|
||||
drop(profiles);
|
||||
|
||||
let (config, exists_keys, logs) = enhance_config(
|
||||
clash_config,
|
||||
profile_activate.current,
|
||||
profile_activate.chain,
|
||||
profile_activate.valid,
|
||||
tun_mode,
|
||||
);
|
||||
|
||||
let mut runtime = self.runtime.lock();
|
||||
*runtime = RuntimeResult {
|
||||
config: Some(config.clone()),
|
||||
config_yaml: Some(serde_yaml::to_string(&config).unwrap_or("".into())),
|
||||
exists_keys,
|
||||
chain_logs: logs,
|
||||
};
|
||||
drop(runtime);
|
||||
|
||||
let mut service = self.service.lock();
|
||||
service.check_start()?;
|
||||
drop(service);
|
||||
|
||||
let handle = self.handle.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
match Service::set_config(clash_info, config).await {
|
||||
Ok(_) => {
|
||||
let handle = handle.lock();
|
||||
handle.refresh_clash();
|
||||
handle.notice_message("set_config::ok".into(), "ok".into());
|
||||
}
|
||||
Err(err) => {
|
||||
let handle = handle.lock();
|
||||
handle.notice_message("set_config::error".into(), format!("{err}"));
|
||||
log::error!(target: "app", "{err}")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Static function
|
||||
/// update profile item
|
||||
pub async fn update_profile_item(&self, uid: String, option: Option<PrfOption>) -> Result<()> {
|
||||
let global = Data::global();
|
||||
|
||||
let (url, opt) = {
|
||||
let profiles = global.profiles.lock();
|
||||
let item = profiles.get_item(&uid)?;
|
||||
|
||||
if let Some(typ) = item.itype.as_ref() {
|
||||
// maybe only valid for `local` profile
|
||||
if *typ != "remote" {
|
||||
// reactivate the config
|
||||
if Some(uid) == profiles.get_current() {
|
||||
drop(profiles);
|
||||
self.activate()?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
if item.url.is_none() {
|
||||
bail!("failed to get the profile item url");
|
||||
}
|
||||
(item.url.clone().unwrap(), item.option.clone())
|
||||
};
|
||||
|
||||
let merged_opt = PrfOption::merge(opt, option);
|
||||
let item = PrfItem::from_url(&url, None, None, merged_opt).await?;
|
||||
|
||||
let mut profiles = global.profiles.lock();
|
||||
profiles.update_item(uid.clone(), item)?;
|
||||
|
||||
// reactivate the profile
|
||||
if Some(uid) == profiles.get_current() {
|
||||
drop(profiles);
|
||||
self.activate()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
pub use self::core::*;
|
||||
|
@ -1,537 +0,0 @@
|
||||
use crate::data::{ClashInfo, Data};
|
||||
use crate::log_if_err;
|
||||
use crate::utils::{config, dirs};
|
||||
use anyhow::{bail, Result};
|
||||
use parking_lot::RwLock;
|
||||
use reqwest::header::HeaderMap;
|
||||
use serde_yaml::Mapping;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::sync::Arc;
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
time::Duration,
|
||||
};
|
||||
use tauri::api::process::{Command, CommandChild, CommandEvent};
|
||||
use tokio::time::sleep;
|
||||
|
||||
const LOGS_QUEUE_LEN: usize = 100;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Service {
|
||||
sidecar: Option<CommandChild>,
|
||||
|
||||
logs: Arc<RwLock<VecDeque<String>>>,
|
||||
|
||||
#[allow(unused)]
|
||||
use_service_mode: bool,
|
||||
}
|
||||
|
||||
impl Service {
|
||||
pub fn new() -> Service {
|
||||
let queue = VecDeque::with_capacity(LOGS_QUEUE_LEN + 10);
|
||||
|
||||
Service {
|
||||
sidecar: None,
|
||||
logs: Arc::new(RwLock::new(queue)),
|
||||
use_service_mode: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&mut self) -> Result<()> {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
self.start_clash_by_sidecar()?;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let enable = {
|
||||
let data = Data::global();
|
||||
let verge = data.verge.lock();
|
||||
verge.enable_service_mode.clone().unwrap_or(false)
|
||||
};
|
||||
|
||||
self.use_service_mode = enable;
|
||||
|
||||
if !enable {
|
||||
return self.start_clash_by_sidecar();
|
||||
}
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
match Self::check_service().await {
|
||||
Ok(status) => {
|
||||
// 未启动clash
|
||||
if status.code != 0 {
|
||||
log_if_err!(Self::start_clash_by_service().await);
|
||||
}
|
||||
}
|
||||
Err(err) => log::error!(target: "app", "{err}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn stop(&mut self) -> Result<()> {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
self.stop_clash_by_sidecar()?;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let _ = self.stop_clash_by_sidecar();
|
||||
|
||||
if self.use_service_mode {
|
||||
tauri::async_runtime::block_on(async move {
|
||||
log_if_err!(Self::stop_clash_by_service().await);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn restart(&mut self) -> Result<()> {
|
||||
self.stop()?;
|
||||
self.start()
|
||||
}
|
||||
|
||||
pub fn get_logs(&self) -> VecDeque<String> {
|
||||
self.logs.read().clone()
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn set_logs(&self, text: String) {
|
||||
let mut logs = self.logs.write();
|
||||
if logs.len() > LOGS_QUEUE_LEN {
|
||||
(*logs).pop_front();
|
||||
}
|
||||
(*logs).push_back(text);
|
||||
}
|
||||
|
||||
pub fn clear_logs(&self) {
|
||||
let mut logs = self.logs.write();
|
||||
(*logs).clear();
|
||||
}
|
||||
|
||||
/// start the clash sidecar
|
||||
fn start_clash_by_sidecar(&mut self) -> Result<()> {
|
||||
if self.sidecar.is_some() {
|
||||
let sidecar = self.sidecar.take().unwrap();
|
||||
let _ = sidecar.kill();
|
||||
}
|
||||
|
||||
let clash_core: String = {
|
||||
let global = Data::global();
|
||||
let verge = global.verge.lock();
|
||||
verge.clash_core.clone().unwrap_or("clash".into())
|
||||
};
|
||||
|
||||
let app_dir = dirs::app_home_dir();
|
||||
let app_dir = app_dir.as_os_str().to_str().unwrap();
|
||||
|
||||
// fix #212
|
||||
let args = match clash_core.as_str() {
|
||||
"clash-meta" => vec!["-m", "-d", app_dir],
|
||||
_ => vec!["-d", app_dir],
|
||||
};
|
||||
|
||||
let cmd = Command::new_sidecar(clash_core)?;
|
||||
|
||||
let (mut rx, cmd_child) = cmd.args(args).spawn()?;
|
||||
|
||||
// 将pid写入文件中
|
||||
let pid = cmd_child.pid();
|
||||
log_if_err!(|| -> Result<()> {
|
||||
let path = dirs::clash_pid_path();
|
||||
fs::File::create(path)?.write(format!("{pid}").as_bytes())?;
|
||||
Ok(())
|
||||
}());
|
||||
|
||||
self.sidecar = Some(cmd_child);
|
||||
|
||||
// clash log
|
||||
let logs = self.logs.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let write_log = |text: String| {
|
||||
let mut logs = logs.write();
|
||||
if logs.len() >= LOGS_QUEUE_LEN {
|
||||
(*logs).pop_front();
|
||||
}
|
||||
(*logs).push_back(text);
|
||||
};
|
||||
|
||||
while let Some(event) = rx.recv().await {
|
||||
match event {
|
||||
CommandEvent::Stdout(line) => {
|
||||
let can_short = line.starts_with("time=") && line.len() > 33;
|
||||
let stdout = if can_short { &line[33..] } else { &line };
|
||||
log::info!(target: "app" ,"[clash]: {}", stdout);
|
||||
write_log(line);
|
||||
}
|
||||
CommandEvent::Stderr(err) => {
|
||||
log::error!(target: "app" ,"[clash error]: {}", err);
|
||||
write_log(err);
|
||||
}
|
||||
CommandEvent::Error(err) => log::error!(target: "app" ,"{err}"),
|
||||
CommandEvent::Terminated(_) => break,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// stop the clash sidecar
|
||||
fn stop_clash_by_sidecar(&mut self) -> Result<()> {
|
||||
if let Some(sidecar) = self.sidecar.take() {
|
||||
sidecar.kill()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_start(&mut self) -> Result<()> {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let global = Data::global();
|
||||
let verge = global.verge.lock();
|
||||
let service_mode = verge.enable_service_mode.unwrap_or(false);
|
||||
|
||||
if !service_mode && self.sidecar.is_none() {
|
||||
self.start()?;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
if self.sidecar.is_none() {
|
||||
self.start()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// update clash config
|
||||
/// using PUT methods
|
||||
pub async fn set_config(info: ClashInfo, config: Mapping) -> Result<()> {
|
||||
let temp_path = dirs::profiles_temp_path();
|
||||
config::save_yaml(temp_path.clone(), &config, Some("# Clash Verge Temp File"))?;
|
||||
|
||||
let (server, headers) = Self::clash_client_info(info)?;
|
||||
|
||||
let mut data = HashMap::new();
|
||||
data.insert("path", temp_path.as_os_str().to_str().unwrap());
|
||||
|
||||
macro_rules! report_err {
|
||||
($i: expr, $e: expr) => {
|
||||
match $i {
|
||||
4 => bail!($e),
|
||||
_ => log::error!(target: "app", $e),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// retry 5 times
|
||||
for i in 0..5 {
|
||||
let headers = headers.clone();
|
||||
match reqwest::ClientBuilder::new().no_proxy().build() {
|
||||
Ok(client) => {
|
||||
let builder = client.put(&server).headers(headers).json(&data);
|
||||
match builder.send().await {
|
||||
Ok(resp) => match resp.status().as_u16() {
|
||||
204 => break,
|
||||
// 配置有问题不重试
|
||||
400 => bail!("failed to update clash config with status 400"),
|
||||
status @ _ => {
|
||||
report_err!(i, "failed to activate clash with status \"{status}\"")
|
||||
}
|
||||
},
|
||||
Err(err) => report_err!(i, "{err}"),
|
||||
}
|
||||
}
|
||||
Err(err) => report_err!(i, "{err}"),
|
||||
}
|
||||
sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// patch clash config
|
||||
pub async fn patch_config(info: ClashInfo, config: Mapping) -> Result<()> {
|
||||
let (server, headers) = Self::clash_client_info(info)?;
|
||||
|
||||
let client = reqwest::ClientBuilder::new().no_proxy().build()?;
|
||||
let builder = client.patch(&server).headers(headers.clone()).json(&config);
|
||||
builder.send().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// get clash client url and headers from clash info
|
||||
fn clash_client_info(info: ClashInfo) -> Result<(String, HeaderMap)> {
|
||||
if info.server.is_none() {
|
||||
let status = &info.status;
|
||||
if info.port.is_none() {
|
||||
bail!("failed to parse config.yaml file with status {status}");
|
||||
} else {
|
||||
bail!("failed to parse the server with status {status}");
|
||||
}
|
||||
}
|
||||
|
||||
let server = info.server.unwrap();
|
||||
let server = format!("http://{server}/configs");
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("Content-Type", "application/json".parse().unwrap());
|
||||
|
||||
if let Some(secret) = info.secret.as_ref() {
|
||||
let secret = format!("Bearer {}", secret.clone()).parse().unwrap();
|
||||
headers.insert("Authorization", secret);
|
||||
}
|
||||
|
||||
Ok((server, headers))
|
||||
}
|
||||
|
||||
/// kill old clash process
|
||||
pub fn kill_old_clash() {
|
||||
use sysinfo::{Pid, PidExt, ProcessExt, System, SystemExt};
|
||||
|
||||
if let Ok(pid) = fs::read(dirs::clash_pid_path()) {
|
||||
if let Ok(pid) = String::from_utf8_lossy(&pid).parse() {
|
||||
let mut system = System::new();
|
||||
system.refresh_all();
|
||||
|
||||
let proc = system.process(Pid::from_u32(pid));
|
||||
if let Some(proc) = proc {
|
||||
proc.kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Service {
|
||||
fn drop(&mut self) {
|
||||
log_if_err!(self.stop());
|
||||
}
|
||||
}
|
||||
|
||||
/// ### Service Mode
|
||||
///
|
||||
#[cfg(target_os = "windows")]
|
||||
pub mod win_service {
|
||||
use super::*;
|
||||
use anyhow::Context;
|
||||
use deelevate::{PrivilegeLevel, Token};
|
||||
use runas::Command as RunasCommand;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::os::windows::process::CommandExt;
|
||||
use std::{env::current_exe, process::Command as StdCommand};
|
||||
|
||||
const SERVICE_NAME: &str = "clash_verge_service";
|
||||
|
||||
const SERVICE_URL: &str = "http://127.0.0.1:33211";
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct ResponseBody {
|
||||
pub bin_path: String,
|
||||
pub config_dir: String,
|
||||
pub log_file: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct JsonResponse {
|
||||
pub code: u64,
|
||||
pub msg: String,
|
||||
pub data: Option<ResponseBody>,
|
||||
}
|
||||
|
||||
impl Service {
|
||||
/// Install the Clash Verge Service
|
||||
/// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程
|
||||
pub async fn install_service() -> Result<()> {
|
||||
let binary_path = dirs::service_path();
|
||||
let install_path = binary_path.with_file_name("install-service.exe");
|
||||
|
||||
if !install_path.exists() {
|
||||
bail!("installer exe not found");
|
||||
}
|
||||
|
||||
let token = Token::with_current_process()?;
|
||||
let level = token.privilege_level()?;
|
||||
|
||||
let status = match level {
|
||||
PrivilegeLevel::NotPrivileged => RunasCommand::new(install_path).status()?,
|
||||
_ => StdCommand::new(install_path)
|
||||
.creation_flags(0x08000000)
|
||||
.status()?,
|
||||
};
|
||||
|
||||
if !status.success() {
|
||||
bail!(
|
||||
"failed to install service with status {}",
|
||||
status.code().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Uninstall the Clash Verge Service
|
||||
/// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程
|
||||
pub async fn uninstall_service() -> Result<()> {
|
||||
let binary_path = dirs::service_path();
|
||||
let uninstall_path = binary_path.with_file_name("uninstall-service.exe");
|
||||
|
||||
if !uninstall_path.exists() {
|
||||
bail!("uninstaller exe not found");
|
||||
}
|
||||
|
||||
let token = Token::with_current_process()?;
|
||||
let level = token.privilege_level()?;
|
||||
|
||||
let status = match level {
|
||||
PrivilegeLevel::NotPrivileged => RunasCommand::new(uninstall_path).status()?,
|
||||
_ => StdCommand::new(uninstall_path)
|
||||
.creation_flags(0x08000000)
|
||||
.status()?,
|
||||
};
|
||||
|
||||
if !status.success() {
|
||||
bail!(
|
||||
"failed to uninstall service with status {}",
|
||||
status.code().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// [deprecated]
|
||||
/// start service
|
||||
/// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程
|
||||
pub async fn start_service() -> Result<()> {
|
||||
let token = Token::with_current_process()?;
|
||||
let level = token.privilege_level()?;
|
||||
|
||||
let args = ["start", SERVICE_NAME];
|
||||
|
||||
let status = match level {
|
||||
PrivilegeLevel::NotPrivileged => RunasCommand::new("sc").args(&args).status()?,
|
||||
_ => StdCommand::new("sc").args(&args).status()?,
|
||||
};
|
||||
|
||||
match status.success() {
|
||||
true => Ok(()),
|
||||
false => bail!(
|
||||
"failed to start service with status {}",
|
||||
status.code().unwrap()
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// stop service
|
||||
pub async fn stop_service() -> Result<()> {
|
||||
let url = format!("{SERVICE_URL}/stop_service");
|
||||
let res = reqwest::ClientBuilder::new()
|
||||
.no_proxy()
|
||||
.build()?
|
||||
.post(url)
|
||||
.send()
|
||||
.await?
|
||||
.json::<JsonResponse>()
|
||||
.await
|
||||
.context("failed to connect to the Clash Verge Service")?;
|
||||
|
||||
if res.code != 0 {
|
||||
bail!(res.msg);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// check the windows service status
|
||||
pub async fn check_service() -> Result<JsonResponse> {
|
||||
let url = format!("{SERVICE_URL}/get_clash");
|
||||
let response = reqwest::ClientBuilder::new()
|
||||
.no_proxy()
|
||||
.build()?
|
||||
.get(url)
|
||||
.send()
|
||||
.await?
|
||||
.json::<JsonResponse>()
|
||||
.await
|
||||
.context("failed to connect to the Clash Verge Service")?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
/// start the clash by service
|
||||
pub(super) async fn start_clash_by_service() -> Result<()> {
|
||||
let status = Self::check_service().await?;
|
||||
|
||||
if status.code == 0 {
|
||||
Self::stop_clash_by_service().await?;
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
|
||||
let clash_core = {
|
||||
let global = Data::global();
|
||||
let verge = global.verge.lock();
|
||||
verge.clash_core.clone().unwrap_or("clash".into())
|
||||
};
|
||||
|
||||
let clash_bin = format!("{clash_core}.exe");
|
||||
let bin_path = current_exe().unwrap().with_file_name(clash_bin);
|
||||
let bin_path = bin_path.as_os_str().to_str().unwrap();
|
||||
|
||||
let config_dir = dirs::app_home_dir();
|
||||
let config_dir = config_dir.as_os_str().to_str().unwrap();
|
||||
|
||||
let log_path = dirs::service_log_file();
|
||||
let log_path = log_path.as_os_str().to_str().unwrap();
|
||||
|
||||
let mut map = HashMap::new();
|
||||
map.insert("bin_path", bin_path);
|
||||
map.insert("config_dir", config_dir);
|
||||
map.insert("log_file", log_path);
|
||||
|
||||
let url = format!("{SERVICE_URL}/start_clash");
|
||||
let res = reqwest::ClientBuilder::new()
|
||||
.no_proxy()
|
||||
.build()?
|
||||
.post(url)
|
||||
.json(&map)
|
||||
.send()
|
||||
.await?
|
||||
.json::<JsonResponse>()
|
||||
.await
|
||||
.context("failed to connect to the Clash Verge Service")?;
|
||||
|
||||
if res.code != 0 {
|
||||
bail!(res.msg);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// stop the clash by service
|
||||
pub(super) async fn stop_clash_by_service() -> Result<()> {
|
||||
let url = format!("{SERVICE_URL}/stop_clash");
|
||||
let res = reqwest::ClientBuilder::new()
|
||||
.no_proxy()
|
||||
.build()?
|
||||
.post(url)
|
||||
.send()
|
||||
.await?
|
||||
.json::<JsonResponse>()
|
||||
.await
|
||||
.context("failed to connect to the Clash Verge Service")?;
|
||||
|
||||
if res.code != 0 {
|
||||
bail!(res.msg);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +1,25 @@
|
||||
use crate::{data::*, log_if_err};
|
||||
use crate::{config::Config, log_err};
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use auto_launch::{AutoLaunch, AutoLaunchBuilder};
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
use sysproxy::Sysproxy;
|
||||
use tauri::{async_runtime::Mutex, utils::platform::current_exe};
|
||||
use tauri::{async_runtime::Mutex as TokioMutex, utils::platform::current_exe};
|
||||
|
||||
pub struct Sysopt {
|
||||
/// current system proxy setting
|
||||
cur_sysproxy: Option<Sysproxy>,
|
||||
cur_sysproxy: Arc<Mutex<Option<Sysproxy>>>,
|
||||
|
||||
/// record the original system proxy
|
||||
/// recover it when exit
|
||||
old_sysproxy: Option<Sysproxy>,
|
||||
old_sysproxy: Arc<Mutex<Option<Sysproxy>>>,
|
||||
|
||||
/// helps to auto launch the app
|
||||
auto_launch: Option<AutoLaunch>,
|
||||
auto_launch: Arc<Mutex<Option<AutoLaunch>>>,
|
||||
|
||||
/// record whether the guard async is running or not
|
||||
guard_state: Arc<Mutex<bool>>,
|
||||
guard_state: Arc<TokioMutex<bool>>,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
@ -28,44 +30,49 @@ static DEFAULT_BYPASS: &str = "localhost,127.0.0.1/8,::1";
|
||||
static DEFAULT_BYPASS: &str = "127.0.0.1,localhost,<local>";
|
||||
|
||||
impl Sysopt {
|
||||
pub fn new() -> Sysopt {
|
||||
Sysopt {
|
||||
cur_sysproxy: None,
|
||||
old_sysproxy: None,
|
||||
auto_launch: None,
|
||||
guard_state: Arc::new(Mutex::new(false)),
|
||||
}
|
||||
pub fn global() -> &'static Sysopt {
|
||||
static SYSOPT: OnceCell<Sysopt> = OnceCell::new();
|
||||
|
||||
SYSOPT.get_or_init(|| Sysopt {
|
||||
cur_sysproxy: Arc::new(Mutex::new(None)),
|
||||
old_sysproxy: Arc::new(Mutex::new(None)),
|
||||
auto_launch: Arc::new(Mutex::new(None)),
|
||||
guard_state: Arc::new(TokioMutex::new(false)),
|
||||
})
|
||||
}
|
||||
|
||||
/// init the sysproxy
|
||||
pub fn init_sysproxy(&mut self) -> Result<()> {
|
||||
let data = Data::global();
|
||||
let clash = data.clash.lock();
|
||||
let port = clash.info.port.clone();
|
||||
pub fn init_sysproxy(&self) -> Result<()> {
|
||||
let port = { Config::clash().latest().get_info()?.port };
|
||||
|
||||
if port.is_none() {
|
||||
bail!("clash port is none");
|
||||
}
|
||||
|
||||
let verge = data.verge.lock();
|
||||
|
||||
let enable = verge.enable_system_proxy.clone().unwrap_or(false);
|
||||
let bypass = verge.system_proxy_bypass.clone();
|
||||
let bypass = bypass.unwrap_or(DEFAULT_BYPASS.into());
|
||||
|
||||
let port = port.unwrap().parse::<u16>()?;
|
||||
let host = String::from("127.0.0.1");
|
||||
|
||||
self.cur_sysproxy = Some(Sysproxy {
|
||||
let (enable, bypass) = {
|
||||
let verge = Config::verge();
|
||||
let verge = verge.latest();
|
||||
(
|
||||
verge.enable_system_proxy.clone().unwrap_or(false),
|
||||
verge.system_proxy_bypass.clone(),
|
||||
)
|
||||
};
|
||||
|
||||
let current = Sysproxy {
|
||||
enable,
|
||||
host,
|
||||
host: String::from("127.0.0.1"),
|
||||
port,
|
||||
bypass,
|
||||
});
|
||||
bypass: bypass.unwrap_or(DEFAULT_BYPASS.into()),
|
||||
};
|
||||
|
||||
if enable {
|
||||
self.old_sysproxy = Sysproxy::get_system_proxy().map_or(None, |p| Some(p));
|
||||
self.cur_sysproxy.as_ref().unwrap().set_system_proxy()?;
|
||||
let old = Sysproxy::get_system_proxy().map_or(None, |p| Some(p));
|
||||
current.set_system_proxy()?;
|
||||
|
||||
*self.old_sysproxy.lock() = old;
|
||||
*self.cur_sysproxy.lock() = Some(current);
|
||||
}
|
||||
|
||||
// run the system proxy guard
|
||||
@ -74,37 +81,46 @@ impl Sysopt {
|
||||
}
|
||||
|
||||
/// update the system proxy
|
||||
pub fn update_sysproxy(&mut self) -> Result<()> {
|
||||
if self.cur_sysproxy.is_none() || self.old_sysproxy.is_none() {
|
||||
pub fn update_sysproxy(&self) -> Result<()> {
|
||||
let mut cur_sysproxy = self.cur_sysproxy.lock();
|
||||
let old_sysproxy = self.old_sysproxy.lock();
|
||||
|
||||
if cur_sysproxy.is_none() || old_sysproxy.is_none() {
|
||||
drop(cur_sysproxy);
|
||||
drop(old_sysproxy);
|
||||
return self.init_sysproxy();
|
||||
}
|
||||
|
||||
let data = Data::global();
|
||||
let verge = data.verge.lock();
|
||||
|
||||
let enable = verge.enable_system_proxy.clone().unwrap_or(false);
|
||||
let bypass = verge.system_proxy_bypass.clone();
|
||||
let bypass = bypass.unwrap_or(DEFAULT_BYPASS.into());
|
||||
|
||||
let mut sysproxy = self.cur_sysproxy.take().unwrap();
|
||||
let (enable, bypass) = {
|
||||
let verge = Config::verge();
|
||||
let verge = verge.latest();
|
||||
(
|
||||
verge.enable_system_proxy.clone().unwrap_or(false),
|
||||
verge.system_proxy_bypass.clone(),
|
||||
)
|
||||
};
|
||||
let mut sysproxy = cur_sysproxy.take().unwrap();
|
||||
|
||||
sysproxy.enable = enable;
|
||||
sysproxy.bypass = bypass;
|
||||
sysproxy.bypass = bypass.unwrap_or(DEFAULT_BYPASS.into());
|
||||
|
||||
self.cur_sysproxy = Some(sysproxy);
|
||||
self.cur_sysproxy.as_ref().unwrap().set_system_proxy()?;
|
||||
sysproxy.set_system_proxy()?;
|
||||
*cur_sysproxy = Some(sysproxy);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// reset the sysproxy
|
||||
pub fn reset_sysproxy(&mut self) -> Result<()> {
|
||||
let cur = self.cur_sysproxy.take();
|
||||
pub fn reset_sysproxy(&self) -> Result<()> {
|
||||
let mut cur_sysproxy = self.cur_sysproxy.lock();
|
||||
let mut old_sysproxy = self.old_sysproxy.lock();
|
||||
|
||||
if let Some(mut old) = self.old_sysproxy.take() {
|
||||
let cur_sysproxy = cur_sysproxy.take();
|
||||
|
||||
if let Some(mut old) = old_sysproxy.take() {
|
||||
// 如果原代理和当前代理 端口一致,就disable关闭,否则就恢复原代理设置
|
||||
// 当前没有设置代理的时候,不确定旧设置是否和当前一致,全关了
|
||||
let port_same = cur.map_or(true, |cur| old.port == cur.port);
|
||||
let port_same = cur_sysproxy.map_or(true, |cur| old.port == cur.port);
|
||||
|
||||
if old.enable && port_same {
|
||||
old.enable = false;
|
||||
@ -114,7 +130,7 @@ impl Sysopt {
|
||||
}
|
||||
|
||||
old.set_system_proxy()?;
|
||||
} else if let Some(mut cur @ Sysproxy { enable: true, .. }) = cur {
|
||||
} else if let Some(mut cur @ Sysproxy { enable: true, .. }) = cur_sysproxy {
|
||||
// 没有原代理,就按现在的代理设置disable即可
|
||||
log::info!(target: "app", "reset proxy by disabling the current proxy");
|
||||
cur.enable = false;
|
||||
@ -127,10 +143,9 @@ impl Sysopt {
|
||||
}
|
||||
|
||||
/// init the auto launch
|
||||
pub fn init_launch(&mut self) -> Result<()> {
|
||||
let data = Data::global();
|
||||
let verge = data.verge.lock();
|
||||
let enable = verge.enable_auto_launch.clone().unwrap_or(false);
|
||||
pub fn init_launch(&self) -> Result<()> {
|
||||
let enable = { Config::verge().latest().enable_auto_launch.clone() };
|
||||
let enable = enable.unwrap_or(false);
|
||||
|
||||
let app_exe = current_exe()?;
|
||||
let app_exe = dunce::canonicalize(app_exe)?;
|
||||
@ -167,51 +182,39 @@ impl Sysopt {
|
||||
.set_app_path(&app_path)
|
||||
.build()?;
|
||||
|
||||
self.auto_launch = Some(auto);
|
||||
|
||||
// 避免在开发时将自启动关了
|
||||
#[cfg(feature = "verge-dev")]
|
||||
if !enable {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let auto = self.auto_launch.as_ref().unwrap();
|
||||
|
||||
// macos每次启动都更新登录项,避免重复设置登录项
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let _ = auto.disable();
|
||||
|
||||
if enable {
|
||||
auto.enable()?;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
match enable {
|
||||
true => auto.enable()?,
|
||||
false => auto.disable()?,
|
||||
};
|
||||
}
|
||||
*self.auto_launch.lock() = Some(auto);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// update the startup
|
||||
pub fn update_launch(&mut self) -> Result<()> {
|
||||
if self.auto_launch.is_none() {
|
||||
pub fn update_launch(&self) -> Result<()> {
|
||||
let auto_launch = self.auto_launch.lock();
|
||||
|
||||
if auto_launch.is_none() {
|
||||
drop(auto_launch);
|
||||
return self.init_launch();
|
||||
}
|
||||
|
||||
let data = Data::global();
|
||||
let verge = data.verge.lock();
|
||||
let enable = verge.enable_auto_launch.clone().unwrap_or(false);
|
||||
|
||||
let auto_launch = self.auto_launch.as_ref().unwrap();
|
||||
let enable = { Config::verge().latest().enable_auto_launch.clone() };
|
||||
let enable = enable.unwrap_or(false);
|
||||
let auto_launch = auto_launch.as_ref().unwrap();
|
||||
|
||||
match enable {
|
||||
true => auto_launch.enable()?,
|
||||
false => crate::log_if_err!(auto_launch.disable()), // 忽略关闭的错误
|
||||
false => log_err!(auto_launch.disable()), // 忽略关闭的错误
|
||||
};
|
||||
|
||||
Ok(())
|
||||
@ -239,14 +242,16 @@ impl Sysopt {
|
||||
loop {
|
||||
sleep(Duration::from_secs(wait_secs)).await;
|
||||
|
||||
let global = Data::global();
|
||||
let verge = global.verge.lock();
|
||||
|
||||
let enable = verge.enable_system_proxy.clone().unwrap_or(false);
|
||||
let guard = verge.enable_proxy_guard.clone().unwrap_or(false);
|
||||
let guard_duration = verge.proxy_guard_duration.clone().unwrap_or(10);
|
||||
let bypass = verge.system_proxy_bypass.clone();
|
||||
drop(verge);
|
||||
let (enable, guard, guard_duration, bypass) = {
|
||||
let verge = Config::verge();
|
||||
let verge = verge.latest();
|
||||
(
|
||||
verge.enable_system_proxy.clone().unwrap_or(false),
|
||||
verge.enable_proxy_guard.clone().unwrap_or(false),
|
||||
verge.proxy_guard_duration.clone().unwrap_or(10),
|
||||
verge.system_proxy_bypass.clone(),
|
||||
)
|
||||
};
|
||||
|
||||
// stop loop
|
||||
if !enable || !guard {
|
||||
@ -256,14 +261,10 @@ impl Sysopt {
|
||||
// update duration
|
||||
wait_secs = guard_duration;
|
||||
|
||||
let clash = global.clash.lock();
|
||||
let port = clash.info.port.clone();
|
||||
let port = port.unwrap_or("".into()).parse::<u16>();
|
||||
drop(clash);
|
||||
|
||||
log::debug!(target: "app", "try to guard the system proxy");
|
||||
|
||||
match port {
|
||||
if let Ok(info) = { Config::clash().latest().get_info() } {
|
||||
match info.port.unwrap_or("".into()).parse::<u16>() {
|
||||
Ok(port) => {
|
||||
let sysproxy = Sysproxy {
|
||||
enable: true,
|
||||
@ -272,14 +273,18 @@ impl Sysopt {
|
||||
bypass: bypass.unwrap_or(DEFAULT_BYPASS.into()),
|
||||
};
|
||||
|
||||
log_if_err!(sysproxy.set_system_proxy());
|
||||
log_err!(sysproxy.set_system_proxy());
|
||||
}
|
||||
Err(_) => {
|
||||
log::error!(target: "app", "failed to parse clash port in guard proxy")
|
||||
}
|
||||
}
|
||||
Err(_) => log::error!(target: "app", "failed to parse clash port"),
|
||||
}
|
||||
}
|
||||
|
||||
let mut state = guard_state.lock().await;
|
||||
*state = false;
|
||||
drop(state);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,96 +1,104 @@
|
||||
use super::Core;
|
||||
use crate::utils::help::get_now;
|
||||
use crate::{data::Data, log_if_err};
|
||||
use crate::config::Config;
|
||||
use crate::feat;
|
||||
use anyhow::{Context, Result};
|
||||
use delay_timer::prelude::{DelayTimer, DelayTimerBuilder, TaskBuilder};
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
type TaskID = u64;
|
||||
|
||||
pub struct Timer {
|
||||
/// cron manager
|
||||
delay_timer: DelayTimer,
|
||||
delay_timer: Arc<Mutex<DelayTimer>>,
|
||||
|
||||
/// save the current state
|
||||
timer_map: HashMap<String, (TaskID, u64)>,
|
||||
timer_map: Arc<Mutex<HashMap<String, (TaskID, u64)>>>,
|
||||
|
||||
/// increment id
|
||||
timer_count: TaskID,
|
||||
timer_count: Arc<Mutex<TaskID>>,
|
||||
}
|
||||
|
||||
impl Timer {
|
||||
pub fn new() -> Self {
|
||||
Timer {
|
||||
delay_timer: DelayTimerBuilder::default().build(),
|
||||
timer_map: HashMap::new(),
|
||||
timer_count: 1,
|
||||
}
|
||||
pub fn global() -> &'static Timer {
|
||||
static TIMER: OnceCell<Timer> = OnceCell::new();
|
||||
|
||||
TIMER.get_or_init(|| Timer {
|
||||
delay_timer: Arc::new(Mutex::new(DelayTimerBuilder::default().build())),
|
||||
timer_map: Arc::new(Mutex::new(HashMap::new())),
|
||||
timer_count: Arc::new(Mutex::new(1)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Correctly update all cron tasks
|
||||
pub fn refresh(&mut self) -> Result<()> {
|
||||
let diff_map = self.gen_diff();
|
||||
/// restore timer
|
||||
pub fn init(&self) -> Result<()> {
|
||||
self.refresh()?;
|
||||
|
||||
for (uid, diff) in diff_map.into_iter() {
|
||||
match diff {
|
||||
DiffFlag::Del(tid) => {
|
||||
let _ = self.timer_map.remove(&uid);
|
||||
log_if_err!(self.delay_timer.remove_task(tid));
|
||||
}
|
||||
DiffFlag::Add(tid, val) => {
|
||||
let _ = self.timer_map.insert(uid.clone(), (tid, val));
|
||||
log_if_err!(self.add_task(uid, tid, val));
|
||||
}
|
||||
DiffFlag::Mod(tid, val) => {
|
||||
let _ = self.timer_map.insert(uid.clone(), (tid, val));
|
||||
log_if_err!(self.delay_timer.remove_task(tid));
|
||||
log_if_err!(self.add_task(uid, tid, val));
|
||||
let cur_timestamp = chrono::Local::now().timestamp();
|
||||
|
||||
let timer_map = self.timer_map.lock();
|
||||
let delay_timer = self.delay_timer.lock();
|
||||
|
||||
Config::profiles().latest().get_items().map(|items| {
|
||||
items
|
||||
.iter()
|
||||
.filter_map(|item| {
|
||||
// mins to seconds
|
||||
let interval = ((item.option.as_ref()?.update_interval?) as i64) * 60;
|
||||
let updated = item.updated? as i64;
|
||||
|
||||
if interval > 0 && cur_timestamp - updated >= interval {
|
||||
Some(item)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.for_each(|item| {
|
||||
if let Some(uid) = item.uid.as_ref() {
|
||||
if let Some((task_id, _)) = timer_map.get(uid) {
|
||||
crate::log_err!(delay_timer.advance_task(*task_id));
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// restore timer
|
||||
pub fn restore(&mut self) -> Result<()> {
|
||||
self.refresh()?;
|
||||
/// Correctly update all cron tasks
|
||||
pub fn refresh(&self) -> Result<()> {
|
||||
let diff_map = self.gen_diff();
|
||||
|
||||
let cur_timestamp = get_now(); // seconds
|
||||
let mut timer_map = self.timer_map.lock();
|
||||
let mut delay_timer = self.delay_timer.lock();
|
||||
|
||||
let global = Data::global();
|
||||
let profiles = global.profiles.lock();
|
||||
|
||||
profiles
|
||||
.get_items()
|
||||
.unwrap_or(&vec![])
|
||||
.iter()
|
||||
.filter(|item| item.uid.is_some() && item.updated.is_some() && item.option.is_some())
|
||||
.filter(|item| {
|
||||
// mins to seconds
|
||||
let interval =
|
||||
item.option.as_ref().unwrap().update_interval.unwrap_or(0) as usize * 60;
|
||||
let updated = item.updated.unwrap();
|
||||
return interval > 0 && cur_timestamp - updated >= interval;
|
||||
})
|
||||
.for_each(|item| {
|
||||
let uid = item.uid.as_ref().unwrap();
|
||||
if let Some((task_id, _)) = self.timer_map.get(uid) {
|
||||
log_if_err!(self.delay_timer.advance_task(*task_id));
|
||||
for (uid, diff) in diff_map.into_iter() {
|
||||
match diff {
|
||||
DiffFlag::Del(tid) => {
|
||||
let _ = timer_map.remove(&uid);
|
||||
crate::log_err!(delay_timer.remove_task(tid));
|
||||
}
|
||||
DiffFlag::Add(tid, val) => {
|
||||
let _ = timer_map.insert(uid.clone(), (tid, val));
|
||||
crate::log_err!(self.add_task(&mut delay_timer, uid, tid, val));
|
||||
}
|
||||
DiffFlag::Mod(tid, val) => {
|
||||
let _ = timer_map.insert(uid.clone(), (tid, val));
|
||||
crate::log_err!(delay_timer.remove_task(tid));
|
||||
crate::log_err!(self.add_task(&mut delay_timer, uid, tid, val));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// generate a uid -> update_interval map
|
||||
fn gen_map(&self) -> HashMap<String, u64> {
|
||||
let global = Data::global();
|
||||
let profiles = global.profiles.lock();
|
||||
|
||||
let mut new_map = HashMap::new();
|
||||
|
||||
if let Some(items) = profiles.get_items() {
|
||||
if let Some(items) = Config::profiles().latest().get_items() {
|
||||
for item in items.iter() {
|
||||
if item.option.is_some() {
|
||||
let option = item.option.as_ref().unwrap();
|
||||
@ -107,11 +115,13 @@ impl Timer {
|
||||
}
|
||||
|
||||
/// generate the diff map for refresh
|
||||
fn gen_diff(&mut self) -> HashMap<String, DiffFlag> {
|
||||
fn gen_diff(&self) -> HashMap<String, DiffFlag> {
|
||||
let mut diff_map = HashMap::new();
|
||||
|
||||
let timer_map = self.timer_map.lock();
|
||||
|
||||
let new_map = self.gen_map();
|
||||
let cur_map = &self.timer_map;
|
||||
let cur_map = &timer_map;
|
||||
|
||||
cur_map.iter().for_each(|(uid, (tid, val))| {
|
||||
let new_val = new_map.get(uid).unwrap_or(&0);
|
||||
@ -123,34 +133,36 @@ impl Timer {
|
||||
}
|
||||
});
|
||||
|
||||
let mut count = self.timer_count;
|
||||
let mut count = self.timer_count.lock();
|
||||
|
||||
new_map.iter().for_each(|(uid, val)| {
|
||||
if cur_map.get(uid).is_none() {
|
||||
diff_map.insert(uid.clone(), DiffFlag::Add(count, *val));
|
||||
diff_map.insert(uid.clone(), DiffFlag::Add(*count, *val));
|
||||
|
||||
count += 1;
|
||||
*count += 1;
|
||||
}
|
||||
});
|
||||
|
||||
self.timer_count = count;
|
||||
|
||||
diff_map
|
||||
}
|
||||
|
||||
/// add a cron task
|
||||
fn add_task(&self, uid: String, tid: TaskID, minutes: u64) -> Result<()> {
|
||||
let core = Core::global();
|
||||
|
||||
fn add_task(
|
||||
&self,
|
||||
delay_timer: &mut DelayTimer,
|
||||
uid: String,
|
||||
tid: TaskID,
|
||||
minutes: u64,
|
||||
) -> Result<()> {
|
||||
let task = TaskBuilder::default()
|
||||
.set_task_id(tid)
|
||||
.set_maximum_parallel_runnable_num(1)
|
||||
.set_frequency_repeated_by_minutes(minutes)
|
||||
// .set_frequency_repeated_by_seconds(minutes) // for test
|
||||
.spawn_async_routine(move || Self::async_task(core.to_owned(), uid.to_owned()))
|
||||
.spawn_async_routine(move || Self::async_task(uid.to_owned()))
|
||||
.context("failed to create timer task")?;
|
||||
|
||||
self.delay_timer
|
||||
delay_timer
|
||||
.add_task(task)
|
||||
.context("failed to add timer task")?;
|
||||
|
||||
@ -158,9 +170,9 @@ impl Timer {
|
||||
}
|
||||
|
||||
/// the task runner
|
||||
async fn async_task(core: Core, uid: String) {
|
||||
async fn async_task(uid: String) {
|
||||
log::info!(target: "app", "running timer task `{uid}`");
|
||||
log_if_err!(core.update_profile_item(uid, None).await);
|
||||
crate::log_err!(feat::update_profile(uid, None).await);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{data::Data, feat, utils::resolve};
|
||||
use anyhow::{Ok, Result};
|
||||
use crate::{config::Config, feat, utils::resolve};
|
||||
use anyhow::Result;
|
||||
use tauri::{
|
||||
api, AppHandle, CustomMenuItem, Manager, SystemTrayEvent, SystemTrayMenu, SystemTrayMenuItem,
|
||||
SystemTraySubmenu,
|
||||
@ -9,11 +9,7 @@ pub struct Tray {}
|
||||
|
||||
impl Tray {
|
||||
pub fn tray_menu(app_handle: &AppHandle) -> SystemTrayMenu {
|
||||
let data = Data::global();
|
||||
let zh = {
|
||||
let verge = data.verge.lock();
|
||||
verge.language == Some("zh".into())
|
||||
};
|
||||
let zh = { Config::verge().latest().language == Some("zh".into()) };
|
||||
|
||||
let version = app_handle.package_info().version.to_string();
|
||||
|
||||
@ -75,13 +71,15 @@ impl Tray {
|
||||
}
|
||||
|
||||
pub fn update_part(app_handle: &AppHandle) -> Result<()> {
|
||||
let global = Data::global();
|
||||
let clash = global.clash.lock();
|
||||
let mode = clash
|
||||
.config
|
||||
.get(&serde_yaml::Value::from("mode"))
|
||||
let mode = {
|
||||
Config::clash()
|
||||
.latest()
|
||||
.0
|
||||
.get("mode")
|
||||
.map(|val| val.as_str().unwrap_or("rule"))
|
||||
.unwrap_or("rule");
|
||||
.unwrap_or("rule")
|
||||
.to_owned()
|
||||
};
|
||||
|
||||
let tray = app_handle.tray_handle();
|
||||
|
||||
@ -90,7 +88,8 @@ impl Tray {
|
||||
let _ = tray.get_item("direct_mode").set_selected(mode == "direct");
|
||||
let _ = tray.get_item("script_mode").set_selected(mode == "script");
|
||||
|
||||
let verge = global.verge.lock();
|
||||
let verge = Config::verge();
|
||||
let verge = verge.latest();
|
||||
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
|
||||
let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
|
||||
|
||||
@ -105,7 +104,7 @@ impl Tray {
|
||||
SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() {
|
||||
mode @ ("rule_mode" | "global_mode" | "direct_mode" | "script_mode") => {
|
||||
let mode = &mode[0..mode.len() - 5];
|
||||
feat::change_clash_mode(mode);
|
||||
feat::change_clash_mode(mode.into());
|
||||
}
|
||||
|
||||
"open_window" => resolve::create_window(app_handle),
|
||||
|
173
src-tauri/src/core/win_service.rs
Normal file
173
src-tauri/src/core/win_service.rs
Normal file
@ -0,0 +1,173 @@
|
||||
#![cfg(target_os = "windows")]
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::utils::dirs;
|
||||
use anyhow::{bail, Context, Result};
|
||||
use deelevate::{PrivilegeLevel, Token};
|
||||
use runas::Command as RunasCommand;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::os::windows::process::CommandExt;
|
||||
use std::time::Duration;
|
||||
use std::{env::current_exe, process::Command as StdCommand};
|
||||
use tokio::time::sleep;
|
||||
|
||||
const SERVICE_URL: &str = "http://127.0.0.1:33211";
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct ResponseBody {
|
||||
pub core_type: Option<String>,
|
||||
pub bin_path: String,
|
||||
pub config_dir: String,
|
||||
pub log_file: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct JsonResponse {
|
||||
pub code: u64,
|
||||
pub msg: String,
|
||||
pub data: Option<ResponseBody>,
|
||||
}
|
||||
|
||||
/// Install the Clash Verge Service
|
||||
/// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程
|
||||
pub async fn install_service() -> Result<()> {
|
||||
let binary_path = dirs::service_path();
|
||||
let install_path = binary_path.with_file_name("install-service.exe");
|
||||
|
||||
if !install_path.exists() {
|
||||
bail!("installer exe not found");
|
||||
}
|
||||
|
||||
let token = Token::with_current_process()?;
|
||||
let level = token.privilege_level()?;
|
||||
|
||||
let status = match level {
|
||||
PrivilegeLevel::NotPrivileged => RunasCommand::new(install_path).status()?,
|
||||
_ => StdCommand::new(install_path)
|
||||
.creation_flags(0x08000000)
|
||||
.status()?,
|
||||
};
|
||||
|
||||
if !status.success() {
|
||||
bail!(
|
||||
"failed to install service with status {}",
|
||||
status.code().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Uninstall the Clash Verge Service
|
||||
/// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程
|
||||
pub async fn uninstall_service() -> Result<()> {
|
||||
let binary_path = dirs::service_path();
|
||||
let uninstall_path = binary_path.with_file_name("uninstall-service.exe");
|
||||
|
||||
if !uninstall_path.exists() {
|
||||
bail!("uninstaller exe not found");
|
||||
}
|
||||
|
||||
let token = Token::with_current_process()?;
|
||||
let level = token.privilege_level()?;
|
||||
|
||||
let status = match level {
|
||||
PrivilegeLevel::NotPrivileged => RunasCommand::new(uninstall_path).status()?,
|
||||
_ => StdCommand::new(uninstall_path)
|
||||
.creation_flags(0x08000000)
|
||||
.status()?,
|
||||
};
|
||||
|
||||
if !status.success() {
|
||||
bail!(
|
||||
"failed to uninstall service with status {}",
|
||||
status.code().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// check the windows service status
|
||||
pub async fn check_service() -> Result<JsonResponse> {
|
||||
let url = format!("{SERVICE_URL}/get_clash");
|
||||
let response = reqwest::ClientBuilder::new()
|
||||
.no_proxy()
|
||||
.build()?
|
||||
.get(url)
|
||||
.send()
|
||||
.await?
|
||||
.json::<JsonResponse>()
|
||||
.await
|
||||
.context("failed to connect to the Clash Verge Service")?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
/// start the clash by service
|
||||
pub(super) async fn run_core_by_service() -> Result<()> {
|
||||
let status = check_service().await?;
|
||||
|
||||
if status.code == 0 {
|
||||
stop_core_by_service().await?;
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
|
||||
let clash_core = { Config::verge().latest().clash_core.clone() };
|
||||
let clash_core = clash_core.unwrap_or("clash".into());
|
||||
|
||||
let clash_bin = format!("{clash_core}.exe");
|
||||
let bin_path = current_exe()?.with_file_name(clash_bin);
|
||||
let bin_path = dirs::path_to_str(&bin_path)?;
|
||||
|
||||
let config_dir = dirs::app_home_dir();
|
||||
let config_dir = dirs::path_to_str(&config_dir)?;
|
||||
|
||||
let log_path = dirs::service_log_file();
|
||||
let log_path = dirs::path_to_str(&log_path)?;
|
||||
|
||||
let mut map = HashMap::new();
|
||||
map.insert("core_type", clash_core.as_str());
|
||||
map.insert("bin_path", bin_path);
|
||||
map.insert("config_dir", config_dir);
|
||||
map.insert("log_file", log_path);
|
||||
|
||||
let url = format!("{SERVICE_URL}/start_clash");
|
||||
let res = reqwest::ClientBuilder::new()
|
||||
.no_proxy()
|
||||
.build()?
|
||||
.post(url)
|
||||
.json(&map)
|
||||
.send()
|
||||
.await?
|
||||
.json::<JsonResponse>()
|
||||
.await
|
||||
.context("failed to connect to the Clash Verge Service")?;
|
||||
|
||||
if res.code != 0 {
|
||||
bail!(res.msg);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// stop the clash by service
|
||||
pub(super) async fn stop_core_by_service() -> Result<()> {
|
||||
let url = format!("{SERVICE_URL}/stop_clash");
|
||||
let res = reqwest::ClientBuilder::new()
|
||||
.no_proxy()
|
||||
.build()?
|
||||
.post(url)
|
||||
.send()
|
||||
.await?
|
||||
.json::<JsonResponse>()
|
||||
.await
|
||||
.context("failed to connect to the Clash Verge Service")?;
|
||||
|
||||
if res.code != 0 {
|
||||
bail!(res.msg);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
mod clash;
|
||||
mod prfitem;
|
||||
mod profiles;
|
||||
mod verge;
|
||||
|
||||
pub use self::clash::*;
|
||||
pub use self::prfitem::*;
|
||||
pub use self::profiles::*;
|
||||
pub use self::verge::*;
|
||||
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Data {
|
||||
pub clash: Arc<Mutex<Clash>>,
|
||||
pub verge: Arc<Mutex<Verge>>,
|
||||
pub profiles: Arc<Mutex<Profiles>>,
|
||||
}
|
||||
|
||||
impl Data {
|
||||
pub fn global() -> &'static Data {
|
||||
static DATA: OnceCell<Data> = OnceCell::new();
|
||||
|
||||
DATA.get_or_init(|| Data {
|
||||
clash: Arc::new(Mutex::new(Clash::new())),
|
||||
verge: Arc::new(Mutex::new(Verge::new())),
|
||||
profiles: Arc::new(Mutex::new(Profiles::new())),
|
||||
})
|
||||
}
|
||||
}
|
68
src-tauri/src/enhance/mod.rs
Normal file
68
src-tauri/src/enhance/mod.rs
Normal file
@ -0,0 +1,68 @@
|
||||
mod field;
|
||||
mod merge;
|
||||
mod script;
|
||||
mod tun;
|
||||
|
||||
pub(self) use self::field::*;
|
||||
use self::merge::*;
|
||||
use self::script::*;
|
||||
use self::tun::*;
|
||||
use crate::config::{ChainItem, ChainType};
|
||||
use serde_yaml::Mapping;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
|
||||
type ResultLog = Vec<(String, String)>;
|
||||
|
||||
pub fn enhance_config(
|
||||
clash_config: Mapping,
|
||||
profile_config: Mapping,
|
||||
chain: Vec<ChainItem>,
|
||||
valid: Vec<String>,
|
||||
tun_mode: bool,
|
||||
) -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
||||
let mut config = profile_config;
|
||||
let mut result_map = HashMap::new();
|
||||
let mut exists_keys = use_keys(&config);
|
||||
|
||||
let valid = use_valid_fields(valid);
|
||||
|
||||
chain.into_iter().for_each(|item| match item.data {
|
||||
ChainType::Merge(merge) => {
|
||||
exists_keys.extend(use_keys(&merge));
|
||||
config = use_merge(merge, config.to_owned());
|
||||
config = use_filter(config.to_owned(), &valid);
|
||||
}
|
||||
ChainType::Script(script) => {
|
||||
let mut logs = vec![];
|
||||
|
||||
match use_script(script, config.to_owned()) {
|
||||
Ok((res_config, res_logs)) => {
|
||||
exists_keys.extend(use_keys(&res_config));
|
||||
config = use_filter(res_config, &valid);
|
||||
logs.extend(res_logs);
|
||||
}
|
||||
Err(err) => logs.push(("exception".into(), err.to_string())),
|
||||
}
|
||||
|
||||
result_map.insert(item.uid, logs);
|
||||
}
|
||||
});
|
||||
|
||||
config = use_filter(config, &valid);
|
||||
|
||||
for (key, value) in clash_config.into_iter() {
|
||||
config.insert(key, value);
|
||||
}
|
||||
|
||||
let clash_fields = use_clash_fields();
|
||||
config = use_filter(config, &clash_fields);
|
||||
config = use_tun(config, tun_mode);
|
||||
config = use_sort(config);
|
||||
|
||||
let mut exists_set = HashSet::new();
|
||||
exists_set.extend(exists_keys.into_iter().filter(|s| clash_fields.contains(s)));
|
||||
exists_keys = exists_set.into_iter().collect();
|
||||
|
||||
(config, exists_keys, result_map)
|
||||
}
|
@ -1,102 +1,307 @@
|
||||
//!
|
||||
//! feat mod 里的函数主要用于
|
||||
//! - hotkey 快捷键
|
||||
//! - timer 定时器
|
||||
//! - cmds 页面调用
|
||||
//!
|
||||
use crate::config::*;
|
||||
use crate::core::*;
|
||||
use crate::data::*;
|
||||
use crate::log_if_err;
|
||||
use crate::log_err;
|
||||
use anyhow::{bail, Result};
|
||||
use serde_yaml::{Mapping, Value};
|
||||
|
||||
// 重启clash
|
||||
pub fn restart_clash_core() {
|
||||
let core = Core::global();
|
||||
let mut service = core.service.lock();
|
||||
log_if_err!(service.restart());
|
||||
drop(service);
|
||||
log_if_err!(core.activate());
|
||||
tauri::async_runtime::spawn(async {
|
||||
match CoreManager::global().run_core().await {
|
||||
Ok(_) => {
|
||||
handle::Handle::refresh_clash();
|
||||
handle::Handle::notice_message("set_config::ok", "ok");
|
||||
}
|
||||
Err(err) => {
|
||||
handle::Handle::notice_message("set_config::error", format!("{err}"));
|
||||
log::error!(target:"app", "{err}");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 切换模式
|
||||
pub fn change_clash_mode(mode: &str) {
|
||||
let core = Core::global();
|
||||
log_if_err!(core.update_mode(mode));
|
||||
// 切换模式 rule/global/direct/script mode
|
||||
pub fn change_clash_mode(mode: String) {
|
||||
let mut mapping = Mapping::new();
|
||||
mapping.insert(Value::from("mode"), mode.clone().into());
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
match clash_api::patch_configs(&mapping).await {
|
||||
Ok(_) => {
|
||||
// 更新配置
|
||||
Config::clash().data().patch_config(mapping);
|
||||
|
||||
if let Ok(_) = Config::clash().data().save_config() {
|
||||
handle::Handle::refresh_clash();
|
||||
log_err!(handle::Handle::update_systray_part());
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!(target: "app", "{err}");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 切换系统代理
|
||||
pub fn toggle_system_proxy() {
|
||||
let core = Core::global();
|
||||
let data = Data::global();
|
||||
let enable = Config::verge().draft().enable_system_proxy.clone();
|
||||
let enable = enable.unwrap_or(false);
|
||||
|
||||
let verge = data.verge.lock();
|
||||
let enable = !verge.enable_system_proxy.clone().unwrap_or(false);
|
||||
drop(verge);
|
||||
|
||||
log_if_err!(core.patch_verge(Verge {
|
||||
enable_system_proxy: Some(enable),
|
||||
..Verge::default()
|
||||
}));
|
||||
|
||||
let handle = core.handle.lock();
|
||||
let _ = handle.refresh_verge();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
match patch_verge(IVerge {
|
||||
enable_system_proxy: Some(!enable),
|
||||
..IVerge::default()
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_) => handle::Handle::refresh_verge(),
|
||||
Err(err) => log::error!(target: "app", "{err}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 打开系统代理
|
||||
pub fn enable_system_proxy() {
|
||||
let core = Core::global();
|
||||
log_if_err!(core.patch_verge(Verge {
|
||||
tauri::async_runtime::spawn(async {
|
||||
match patch_verge(IVerge {
|
||||
enable_system_proxy: Some(true),
|
||||
..Verge::default()
|
||||
}));
|
||||
|
||||
let handle = core.handle.lock();
|
||||
let _ = handle.refresh_verge();
|
||||
..IVerge::default()
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_) => handle::Handle::refresh_verge(),
|
||||
Err(err) => log::error!(target: "app", "{err}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭系统代理
|
||||
pub fn disable_system_proxy() {
|
||||
let core = Core::global();
|
||||
log_if_err!(core.patch_verge(Verge {
|
||||
tauri::async_runtime::spawn(async {
|
||||
match patch_verge(IVerge {
|
||||
enable_system_proxy: Some(false),
|
||||
..Verge::default()
|
||||
}));
|
||||
|
||||
let handle = core.handle.lock();
|
||||
let _ = handle.refresh_verge();
|
||||
..IVerge::default()
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_) => handle::Handle::refresh_verge(),
|
||||
Err(err) => log::error!(target: "app", "{err}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 切换tun模式
|
||||
pub fn toggle_tun_mode() {
|
||||
let core = Core::global();
|
||||
let data = Data::global();
|
||||
let enable = Config::verge().data().enable_tun_mode.clone();
|
||||
let enable = enable.unwrap_or(false);
|
||||
|
||||
let verge = data.verge.lock();
|
||||
let enable = !verge.enable_tun_mode.clone().unwrap_or(false);
|
||||
drop(verge);
|
||||
|
||||
log_if_err!(core.patch_verge(Verge {
|
||||
enable_tun_mode: Some(enable),
|
||||
..Verge::default()
|
||||
}));
|
||||
|
||||
let handle = core.handle.lock();
|
||||
let _ = handle.refresh_verge();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
match patch_verge(IVerge {
|
||||
enable_tun_mode: Some(!enable),
|
||||
..IVerge::default()
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_) => handle::Handle::refresh_verge(),
|
||||
Err(err) => log::error!(target: "app", "{err}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 打开tun模式
|
||||
pub fn enable_tun_mode() {
|
||||
let core = Core::global();
|
||||
log_if_err!(core.patch_verge(Verge {
|
||||
tauri::async_runtime::spawn(async {
|
||||
match patch_verge(IVerge {
|
||||
enable_tun_mode: Some(true),
|
||||
..Verge::default()
|
||||
}));
|
||||
|
||||
let handle = core.handle.lock();
|
||||
let _ = handle.refresh_verge();
|
||||
..IVerge::default()
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_) => handle::Handle::refresh_verge(),
|
||||
Err(err) => log::error!(target: "app", "{err}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭tun模式
|
||||
pub fn disable_tun_mode() {
|
||||
let core = Core::global();
|
||||
log_if_err!(core.patch_verge(Verge {
|
||||
tauri::async_runtime::spawn(async {
|
||||
match patch_verge(IVerge {
|
||||
enable_tun_mode: Some(false),
|
||||
..Verge::default()
|
||||
}));
|
||||
|
||||
let handle = core.handle.lock();
|
||||
let _ = handle.refresh_verge();
|
||||
..IVerge::default()
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_) => handle::Handle::refresh_verge(),
|
||||
Err(err) => log::error!(target: "app", "{err}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// 修改clash的配置
|
||||
pub async fn patch_clash(patch: Mapping) -> Result<()> {
|
||||
Config::clash().draft().patch_config(patch.clone());
|
||||
|
||||
match {
|
||||
let mixed_port = patch.get("mixed-port");
|
||||
if mixed_port.is_some() {
|
||||
let changed = mixed_port != Config::clash().data().0.get("mixed-port");
|
||||
// 检查端口占用
|
||||
if changed {
|
||||
if let Some(port) = mixed_port.clone().unwrap().as_u64() {
|
||||
if !port_scanner::local_port_available(port as u16) {
|
||||
Config::clash().discard();
|
||||
bail!("the port not available");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 激活配置
|
||||
if mixed_port.is_some()
|
||||
|| patch.get("secret").is_some()
|
||||
|| patch.get("external-controller").is_some()
|
||||
{
|
||||
CoreManager::global().run_core().await?;
|
||||
}
|
||||
|
||||
// 更新系统代理
|
||||
if mixed_port.is_some() {
|
||||
log_err!(sysopt::Sysopt::global().init_sysproxy());
|
||||
}
|
||||
|
||||
if patch.get("mode").is_some() {
|
||||
log_err!(handle::Handle::update_systray_part());
|
||||
}
|
||||
|
||||
<Result<()>>::Ok(())
|
||||
} {
|
||||
Ok(()) => {
|
||||
Config::clash().apply();
|
||||
Config::clash().data().save_config()?;
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
Config::clash().discard();
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 修改verge的配置
|
||||
/// 一般都是一个个的修改
|
||||
pub async fn patch_verge(patch: IVerge) -> Result<()> {
|
||||
Config::verge().draft().patch_config(patch.clone());
|
||||
|
||||
let tun_mode = patch.enable_tun_mode;
|
||||
let auto_launch = patch.enable_auto_launch;
|
||||
let system_proxy = patch.enable_system_proxy;
|
||||
let proxy_bypass = patch.system_proxy_bypass;
|
||||
let language = patch.language;
|
||||
|
||||
match {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
|
||||
if tun_mode.is_some() {
|
||||
update_core_config().await?;
|
||||
}
|
||||
|
||||
if auto_launch.is_some() {
|
||||
sysopt::Sysopt::global().update_launch()?;
|
||||
}
|
||||
if system_proxy.is_some() || proxy_bypass.is_some() {
|
||||
sysopt::Sysopt::global().update_sysproxy()?;
|
||||
sysopt::Sysopt::global().guard_proxy();
|
||||
}
|
||||
|
||||
if let Some(true) = patch.enable_proxy_guard {
|
||||
sysopt::Sysopt::global().guard_proxy();
|
||||
}
|
||||
|
||||
if let Some(hotkeys) = patch.hotkeys {
|
||||
hotkey::Hotkey::global().update(hotkeys)?;
|
||||
}
|
||||
|
||||
if language.is_some() {
|
||||
handle::Handle::update_systray()?;
|
||||
} else if system_proxy.or(tun_mode).is_some() {
|
||||
handle::Handle::update_systray_part()?;
|
||||
}
|
||||
|
||||
<Result<()>>::Ok(())
|
||||
} {
|
||||
Ok(()) => {
|
||||
Config::verge().apply();
|
||||
Config::verge().data().save_file()?;
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
Config::verge().discard();
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 更新某个profile
|
||||
/// 如果更新当前配置就激活配置
|
||||
pub async fn update_profile(uid: String, option: Option<PrfOption>) -> Result<()> {
|
||||
let url_opt = {
|
||||
let profiles = Config::profiles();
|
||||
let profiles = profiles.latest();
|
||||
let item = profiles.get_item(&uid)?;
|
||||
let is_remote = item.itype.as_ref().map_or(false, |s| s == "remote");
|
||||
|
||||
if !is_remote {
|
||||
None // 直接更新
|
||||
} else if item.url.is_none() {
|
||||
bail!("failed to get the profile item url");
|
||||
} else {
|
||||
Some((item.url.clone().unwrap(), item.option.clone()))
|
||||
}
|
||||
};
|
||||
|
||||
let should_update = match url_opt {
|
||||
Some((url, opt)) => {
|
||||
let merged_opt = PrfOption::merge(opt, option);
|
||||
let item = PrfItem::from_url(&url, None, None, merged_opt).await?;
|
||||
|
||||
let profiles = Config::profiles();
|
||||
let mut profiles = profiles.latest();
|
||||
profiles.update_item(uid.clone(), item)?;
|
||||
|
||||
Some(uid) == profiles.get_current()
|
||||
}
|
||||
None => true,
|
||||
};
|
||||
|
||||
if should_update {
|
||||
update_core_config().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 更新配置
|
||||
async fn update_core_config() -> Result<()> {
|
||||
match CoreManager::global().update_config().await {
|
||||
Ok(_) => {
|
||||
handle::Handle::refresh_clash();
|
||||
handle::Handle::notice_message("set_config::ok", "ok");
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
handle::Handle::notice_message("set_config::error", format!("{err}"));
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,12 +6,12 @@
|
||||
mod cmds;
|
||||
mod config;
|
||||
mod core;
|
||||
mod data;
|
||||
mod enhance;
|
||||
mod feat;
|
||||
mod utils;
|
||||
|
||||
use crate::utils::{init, resolve, server};
|
||||
use tauri::{api, Manager, SystemTray};
|
||||
use tauri::{api, SystemTray};
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
// 单例检测
|
||||
@ -20,18 +20,12 @@ fn main() -> std::io::Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
unsafe {
|
||||
use crate::utils::dirs;
|
||||
dirs::init_portable_flag();
|
||||
}
|
||||
|
||||
crate::log_if_err!(init::init_config());
|
||||
crate::log_err!(init::init_config());
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut builder = tauri::Builder::default()
|
||||
.setup(|app| Ok(resolve::resolve_setup(app)))
|
||||
.system_tray(SystemTray::new())
|
||||
.setup(|app| Ok(resolve::resolve_setup(app)))
|
||||
.on_system_tray_event(core::tray::Tray::on_system_tray_event)
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
// common
|
||||
@ -39,7 +33,7 @@ fn main() -> std::io::Result<()> {
|
||||
cmds::open_app_dir,
|
||||
cmds::open_logs_dir,
|
||||
cmds::open_web_url,
|
||||
cmds::kill_sidecar,
|
||||
// cmds::kill_sidecar,
|
||||
cmds::restart_sidecar,
|
||||
// clash
|
||||
cmds::get_clash_info,
|
||||
@ -53,24 +47,20 @@ fn main() -> std::io::Result<()> {
|
||||
// verge
|
||||
cmds::get_verge_config,
|
||||
cmds::patch_verge_config,
|
||||
cmds::update_hotkeys,
|
||||
// cmds::update_hotkeys,
|
||||
// profile
|
||||
cmds::get_profiles,
|
||||
cmds::enhance_profiles,
|
||||
cmds::patch_profiles_config,
|
||||
cmds::view_profile,
|
||||
cmds::patch_profile,
|
||||
cmds::create_profile,
|
||||
cmds::import_profile,
|
||||
cmds::update_profile,
|
||||
cmds::delete_profile,
|
||||
cmds::select_profile,
|
||||
cmds::get_profiles,
|
||||
cmds::enhance_profiles,
|
||||
cmds::change_profile_chain,
|
||||
cmds::change_profile_valid,
|
||||
cmds::read_profile_file,
|
||||
cmds::save_profile_file,
|
||||
// service mode
|
||||
cmds::service::start_service,
|
||||
cmds::service::stop_service,
|
||||
cmds::service::check_service,
|
||||
cmds::service::install_service,
|
||||
cmds::service::uninstall_service,
|
||||
@ -96,22 +86,10 @@ fn main() -> std::io::Result<()> {
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut app = builder
|
||||
let app = builder
|
||||
.build(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
app.set_activation_policy(tauri::ActivationPolicy::Accessory);
|
||||
|
||||
let app_handle = app.app_handle();
|
||||
ctrlc::set_handler(move || {
|
||||
resolve::resolve_reset();
|
||||
app_handle.exit(0);
|
||||
})
|
||||
.expect("error while exiting.");
|
||||
|
||||
#[allow(unused)]
|
||||
app.run(|app_handle, e| match e {
|
||||
tauri::RunEvent::ExitRequested { api, .. } => {
|
||||
api.prevent_exit();
|
||||
@ -123,6 +101,8 @@ fn main() -> std::io::Result<()> {
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
tauri::RunEvent::WindowEvent { label, event, .. } => {
|
||||
use tauri::Manager;
|
||||
|
||||
if label == "main" {
|
||||
match event {
|
||||
tauri::WindowEvent::CloseRequested { api, .. } => {
|
||||
|
@ -1,59 +0,0 @@
|
||||
use anyhow::{Context, Result};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use serde_yaml::{Mapping, Value};
|
||||
use std::{fs, path::PathBuf};
|
||||
|
||||
/// read data from yaml as struct T
|
||||
pub fn read_yaml<T: DeserializeOwned + Default>(path: PathBuf) -> T {
|
||||
if !path.exists() {
|
||||
log::error!(target: "app", "file not found \"{}\"", path.display());
|
||||
return T::default();
|
||||
}
|
||||
|
||||
let yaml_str = fs::read_to_string(&path).unwrap_or("".into());
|
||||
|
||||
match serde_yaml::from_str::<T>(&yaml_str) {
|
||||
Ok(val) => val,
|
||||
Err(_) => {
|
||||
log::error!(target: "app", "failed to read yaml file \"{}\"", path.display());
|
||||
T::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// read mapping from yaml fix #165
|
||||
pub fn read_merge_mapping(path: PathBuf) -> Mapping {
|
||||
let map = Mapping::new();
|
||||
|
||||
if !path.exists() {
|
||||
log::error!(target: "app", "file not found \"{}\"", path.display());
|
||||
return map;
|
||||
}
|
||||
|
||||
let yaml_str = fs::read_to_string(&path).unwrap_or("".into());
|
||||
|
||||
match serde_yaml::from_str::<Value>(&yaml_str) {
|
||||
Ok(mut val) => {
|
||||
crate::log_if_err!(val.apply_merge());
|
||||
val.as_mapping().unwrap_or(&map).to_owned()
|
||||
}
|
||||
Err(_) => {
|
||||
log::error!(target: "app", "failed to read yaml file \"{}\"", path.display());
|
||||
map
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// save the data to the file
|
||||
/// can set `prefix` string to add some comments
|
||||
pub fn save_yaml<T: Serialize>(path: PathBuf, data: &T, prefix: Option<&str>) -> Result<()> {
|
||||
let data_str = serde_yaml::to_string(data)?;
|
||||
|
||||
let yaml_str = match prefix {
|
||||
Some(prefix) => format!("{prefix}{data_str}"),
|
||||
None => data_str,
|
||||
};
|
||||
|
||||
let path_str = path.as_os_str().to_string_lossy().to_string();
|
||||
fs::write(path, yaml_str.as_bytes()).context(format!("failed to save file \"{path_str}\""))
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
use std::env::temp_dir;
|
||||
use anyhow::Result;
|
||||
use std::path::PathBuf;
|
||||
use tauri::{
|
||||
api::path::{home_dir, resource_dir},
|
||||
@ -13,7 +13,6 @@ static APP_DIR: &str = "clash-verge-dev";
|
||||
static CLASH_CONFIG: &str = "config.yaml";
|
||||
static VERGE_CONFIG: &str = "verge.yaml";
|
||||
static PROFILE_YAML: &str = "profiles.yaml";
|
||||
static PROFILE_TEMP: &str = "clash-verge-runtime.yaml";
|
||||
|
||||
static mut RESOURCE_DIR: Option<PathBuf> = None;
|
||||
|
||||
@ -21,49 +20,58 @@ static mut RESOURCE_DIR: Option<PathBuf> = None;
|
||||
#[allow(unused)]
|
||||
static mut PORTABLE_FLAG: bool = false;
|
||||
|
||||
pub static mut APP_VERSION: &str = "v1.1.1";
|
||||
pub static mut APP_VERSION: &str = "v1.1.2";
|
||||
|
||||
/// initialize portable flag
|
||||
#[allow(unused)]
|
||||
pub unsafe fn init_portable_flag() {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
#[cfg(target_os = "windows")]
|
||||
pub unsafe fn init_portable_flag() -> Result<()> {
|
||||
use tauri::utils::platform::current_exe;
|
||||
|
||||
let exe = current_exe().unwrap();
|
||||
let dir = exe.parent().unwrap();
|
||||
let exe = current_exe()?;
|
||||
|
||||
if let Some(dir) = exe.parent() {
|
||||
let dir = PathBuf::from(dir).join(".config/PORTABLE");
|
||||
|
||||
if dir.exists() {
|
||||
PORTABLE_FLAG = true;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// get the verge app home dir
|
||||
pub fn app_home_dir() -> PathBuf {
|
||||
pub fn app_home_dir() -> Result<PathBuf> {
|
||||
#[cfg(target_os = "windows")]
|
||||
unsafe {
|
||||
use tauri::utils::platform::current_exe;
|
||||
|
||||
if !PORTABLE_FLAG {
|
||||
home_dir().unwrap().join(".config").join(APP_DIR)
|
||||
Ok(home_dir()
|
||||
.ok_or(anyhow::anyhow!("failed to get app home dir"))?
|
||||
.join(".config")
|
||||
.join(APP_DIR))
|
||||
} else {
|
||||
let app_exe = current_exe().unwrap();
|
||||
let app_exe = dunce::canonicalize(app_exe).unwrap();
|
||||
let app_dir = app_exe.parent().unwrap();
|
||||
PathBuf::from(app_dir).join(".config").join(APP_DIR)
|
||||
let app_exe = current_exe()?;
|
||||
let app_exe = dunce::canonicalize(app_exe)?;
|
||||
let app_dir = app_exe
|
||||
.parent()
|
||||
.ok_or(anyhow::anyhow!("failed to get the portable app dir"))?;
|
||||
Ok(PathBuf::from(app_dir).join(".config").join(APP_DIR))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
home_dir().unwrap().join(".config").join(APP_DIR)
|
||||
Ok(home_dir()
|
||||
.ok_or(anyhow::anyhow!("failed to get the app home dir"))?
|
||||
.join(".config")
|
||||
.join(APP_DIR))
|
||||
}
|
||||
|
||||
/// get the resources dir
|
||||
pub fn app_resources_dir(package_info: &PackageInfo) -> PathBuf {
|
||||
pub fn app_resources_dir(package_info: &PackageInfo) -> Result<PathBuf> {
|
||||
let res_dir = resource_dir(package_info, &Env::default())
|
||||
.unwrap()
|
||||
.ok_or(anyhow::anyhow!("failed to get the resource dir"))?
|
||||
.join("resources");
|
||||
|
||||
unsafe {
|
||||
@ -74,51 +82,48 @@ pub fn app_resources_dir(package_info: &PackageInfo) -> PathBuf {
|
||||
APP_VERSION = Box::leak(Box::new(ver_str));
|
||||
}
|
||||
|
||||
res_dir
|
||||
Ok(res_dir)
|
||||
}
|
||||
|
||||
/// profiles dir
|
||||
pub fn app_profiles_dir() -> PathBuf {
|
||||
app_home_dir().join("profiles")
|
||||
pub fn app_profiles_dir() -> Result<PathBuf> {
|
||||
Ok(app_home_dir()?.join("profiles"))
|
||||
}
|
||||
|
||||
/// logs dir
|
||||
pub fn app_logs_dir() -> PathBuf {
|
||||
app_home_dir().join("logs")
|
||||
pub fn app_logs_dir() -> Result<PathBuf> {
|
||||
Ok(app_home_dir()?.join("logs"))
|
||||
}
|
||||
|
||||
pub fn clash_path() -> PathBuf {
|
||||
app_home_dir().join(CLASH_CONFIG)
|
||||
pub fn clash_path() -> Result<PathBuf> {
|
||||
Ok(app_home_dir()?.join(CLASH_CONFIG))
|
||||
}
|
||||
|
||||
pub fn verge_path() -> PathBuf {
|
||||
app_home_dir().join(VERGE_CONFIG)
|
||||
pub fn verge_path() -> Result<PathBuf> {
|
||||
Ok(app_home_dir()?.join(VERGE_CONFIG))
|
||||
}
|
||||
|
||||
pub fn profiles_path() -> PathBuf {
|
||||
app_home_dir().join(PROFILE_YAML)
|
||||
pub fn profiles_path() -> Result<PathBuf> {
|
||||
Ok(app_home_dir()?.join(PROFILE_YAML))
|
||||
}
|
||||
|
||||
pub fn profiles_temp_path() -> PathBuf {
|
||||
#[cfg(not(feature = "debug-yml"))]
|
||||
return temp_dir().join(PROFILE_TEMP);
|
||||
|
||||
#[cfg(feature = "debug-yml")]
|
||||
return app_home_dir().join(PROFILE_TEMP);
|
||||
pub fn app_res_dir() -> Result<PathBuf> {
|
||||
unsafe {
|
||||
Ok(RESOURCE_DIR
|
||||
.clone()
|
||||
.ok_or(anyhow::anyhow!("failed to get the resource dir"))?)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clash_pid_path() -> PathBuf {
|
||||
unsafe { RESOURCE_DIR.clone().unwrap().join("clash.pid") }
|
||||
pub fn clash_pid_path() -> Result<PathBuf> {
|
||||
unsafe { Ok(RESOURCE_DIR.clone().unwrap().join("clash.pid")) }
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
static SERVICE_PATH: &str = "clash-verge-service.exe";
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn service_path() -> PathBuf {
|
||||
unsafe {
|
||||
let res_dir = RESOURCE_DIR.clone().unwrap();
|
||||
res_dir.join(SERVICE_PATH)
|
||||
res_dir.join("clash-verge-service.exe")
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,7 +133,7 @@ pub fn service_log_file() -> PathBuf {
|
||||
|
||||
let log_dir = app_logs_dir().join("service");
|
||||
|
||||
let local_time = Local::now().format("%Y-%m-%d-%H%M%S").to_string();
|
||||
let local_time = Local::now().format("%Y-%m-%d-%H%M").to_string();
|
||||
let log_file = format!("{}.log", local_time);
|
||||
let log_file = log_dir.join(log_file);
|
||||
|
||||
@ -136,3 +141,11 @@ pub fn service_log_file() -> PathBuf {
|
||||
|
||||
log_file
|
||||
}
|
||||
|
||||
pub fn path_to_str(path: &PathBuf) -> Result<&str> {
|
||||
let path_str = path
|
||||
.as_os_str()
|
||||
.to_str()
|
||||
.ok_or(anyhow::anyhow!("failed to get path from {:?}", path))?;
|
||||
Ok(path_str)
|
||||
}
|
||||
|
@ -1,15 +1,51 @@
|
||||
use anyhow::Result;
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use nanoid::nanoid;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::str::FromStr;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use serde_yaml::{Mapping, Value};
|
||||
use std::{fs, path::PathBuf, process::Command, str::FromStr};
|
||||
|
||||
pub fn get_now() -> usize {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as _
|
||||
/// read data from yaml as struct T
|
||||
pub fn read_yaml<T: DeserializeOwned>(path: &PathBuf) -> Result<T> {
|
||||
if !path.exists() {
|
||||
bail!("file not found \"{}\"", path.display());
|
||||
}
|
||||
|
||||
let yaml_str = fs::read_to_string(&path)
|
||||
.context(format!("failed to read the file \"{}\"", path.display()))?;
|
||||
|
||||
serde_yaml::from_str::<T>(&yaml_str).context(format!(
|
||||
"failed to read the file with yaml format \"{}\"",
|
||||
path.display()
|
||||
))
|
||||
}
|
||||
|
||||
/// read mapping from yaml fix #165
|
||||
pub fn read_merge_mapping(path: &PathBuf) -> Result<Mapping> {
|
||||
let mut val: Value = read_yaml(path)?;
|
||||
val.apply_merge()
|
||||
.context(format!("failed to apply merge \"{}\"", path.display()))?;
|
||||
|
||||
Ok(val
|
||||
.as_mapping()
|
||||
.ok_or(anyhow!(
|
||||
"failed to transform to yaml mapping \"{}\"",
|
||||
path.display()
|
||||
))?
|
||||
.to_owned())
|
||||
}
|
||||
|
||||
/// save the data to the file
|
||||
/// can set `prefix` string to add some comments
|
||||
pub fn save_yaml<T: Serialize>(path: &PathBuf, data: &T, prefix: Option<&str>) -> Result<()> {
|
||||
let data_str = serde_yaml::to_string(data)?;
|
||||
|
||||
let yaml_str = match prefix {
|
||||
Some(prefix) => format!("{prefix}\n\n{data_str}"),
|
||||
None => data_str,
|
||||
};
|
||||
|
||||
let path_str = path.as_os_str().to_string_lossy().to_string();
|
||||
fs::write(path, yaml_str.as_bytes()).context(format!("failed to save file \"{path_str}\""))
|
||||
}
|
||||
|
||||
const ALPHABET: [char; 62] = [
|
||||
@ -70,12 +106,25 @@ pub fn open_file(path: PathBuf) -> Result<()> {
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! log_if_err {
|
||||
macro_rules! error {
|
||||
($result: expr) => {
|
||||
log::error!(target: "app", "{}", $result);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! log_err {
|
||||
($result: expr) => {
|
||||
if let Err(err) = $result {
|
||||
log::error!(target: "app", "{err}");
|
||||
}
|
||||
};
|
||||
|
||||
($result: expr, $err_str: expr) => {
|
||||
if let Err(_) = $result {
|
||||
log::error!(target: "app", "{}", $err_str);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// wrap the anyhow error
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::utils::{dirs, tmpl};
|
||||
use crate::utils::dirs;
|
||||
use anyhow::Result;
|
||||
use chrono::Local;
|
||||
use log::LevelFilter;
|
||||
@ -7,27 +7,33 @@ use log4rs::append::file::FileAppender;
|
||||
use log4rs::config::{Appender, Config, Logger, Root};
|
||||
use log4rs::encode::pattern::PatternEncoder;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use tauri::PackageInfo;
|
||||
|
||||
/// initialize this instance's log file
|
||||
fn init_log() -> Result<()> {
|
||||
let log_dir = dirs::app_logs_dir();
|
||||
let log_dir = dirs::app_logs_dir()?;
|
||||
if !log_dir.exists() {
|
||||
let _ = fs::create_dir_all(&log_dir);
|
||||
}
|
||||
|
||||
let local_time = Local::now().format("%Y-%m-%d-%H%M%S").to_string();
|
||||
let local_time = Local::now().format("%Y-%m-%d-%H%M").to_string();
|
||||
let log_file = format!("{}.log", local_time);
|
||||
let log_file = log_dir.join(log_file);
|
||||
|
||||
let time_format = "{d(%Y-%m-%d %H:%M:%S)} - {m}{n}";
|
||||
let stdout = ConsoleAppender::builder()
|
||||
.encoder(Box::new(PatternEncoder::new(time_format)))
|
||||
.build();
|
||||
let tofile = FileAppender::builder()
|
||||
.encoder(Box::new(PatternEncoder::new(time_format)))
|
||||
.build(log_file)?;
|
||||
#[cfg(feature = "verge-dev")]
|
||||
let time_format = "{d(%Y-%m-%d %H:%M:%S)} {l} - {M} {m}{n}";
|
||||
#[cfg(not(feature = "verge-dev"))]
|
||||
let time_format = "{d(%Y-%m-%d %H:%M:%S)} {l} - {m}{n}";
|
||||
|
||||
let encode = Box::new(PatternEncoder::new(time_format));
|
||||
|
||||
let stdout = ConsoleAppender::builder().encoder(encode.clone()).build();
|
||||
let tofile = FileAppender::builder().encoder(encode).build(log_file)?;
|
||||
|
||||
#[cfg(feature = "verge-dev")]
|
||||
let level = LevelFilter::Debug;
|
||||
#[cfg(not(feature = "verge-dev"))]
|
||||
let level = LevelFilter::Info;
|
||||
|
||||
let config = Config::builder()
|
||||
.appender(Appender::builder().build("stdout", Box::new(stdout)))
|
||||
@ -36,9 +42,9 @@ fn init_log() -> Result<()> {
|
||||
Logger::builder()
|
||||
.appenders(["file", "stdout"])
|
||||
.additive(false)
|
||||
.build("app", LevelFilter::Info),
|
||||
.build("app", level),
|
||||
)
|
||||
.build(Root::builder().appender("stdout").build(LevelFilter::Info))?;
|
||||
.build(Root::builder().appender("stdout").build(level))?;
|
||||
|
||||
log4rs::init_config(config)?;
|
||||
|
||||
@ -47,44 +53,32 @@ fn init_log() -> Result<()> {
|
||||
|
||||
/// Initialize all the files from resources
|
||||
pub fn init_config() -> Result<()> {
|
||||
#[cfg(target_os = "windows")]
|
||||
unsafe {
|
||||
let _ = dirs::init_portable_flag();
|
||||
}
|
||||
|
||||
let _ = init_log();
|
||||
|
||||
let app_dir = dirs::app_home_dir();
|
||||
let profiles_dir = dirs::app_profiles_dir();
|
||||
|
||||
let _ = dirs::app_home_dir().map(|app_dir| {
|
||||
if !app_dir.exists() {
|
||||
let _ = fs::create_dir_all(&app_dir);
|
||||
}
|
||||
});
|
||||
|
||||
let _ = dirs::app_profiles_dir().map(|profiles_dir| {
|
||||
if !profiles_dir.exists() {
|
||||
let _ = fs::create_dir_all(&profiles_dir);
|
||||
}
|
||||
});
|
||||
|
||||
// target path
|
||||
let clash_path = app_dir.join("config.yaml");
|
||||
let verge_path = app_dir.join("verge.yaml");
|
||||
let profile_path = app_dir.join("profiles.yaml");
|
||||
|
||||
if !clash_path.exists() {
|
||||
fs::File::create(clash_path)?.write(tmpl::CLASH_CONFIG)?;
|
||||
}
|
||||
if !verge_path.exists() {
|
||||
fs::File::create(verge_path)?.write(tmpl::VERGE_CONFIG)?;
|
||||
}
|
||||
if !profile_path.exists() {
|
||||
fs::File::create(profile_path)?.write(tmpl::PROFILES_CONFIG)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// initialize app
|
||||
pub fn init_resources(package_info: &PackageInfo) {
|
||||
// create app dir
|
||||
let app_dir = dirs::app_home_dir();
|
||||
let res_dir = dirs::app_resources_dir(package_info);
|
||||
|
||||
if !app_dir.exists() {
|
||||
let _ = fs::create_dir_all(&app_dir);
|
||||
}
|
||||
pub fn init_resources(package_info: &PackageInfo) -> Result<()> {
|
||||
let app_dir = dirs::app_home_dir()?;
|
||||
let res_dir = dirs::app_resources_dir(package_info)?;
|
||||
|
||||
// copy the resource file
|
||||
for file in ["Country.mmdb", "geoip.dat", "geosite.dat", "wintun.dll"].iter() {
|
||||
@ -94,4 +88,6 @@ pub fn init_resources(package_info: &PackageInfo) {
|
||||
let _ = fs::copy(src_path, target_path);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
pub mod config;
|
||||
pub mod dirs;
|
||||
pub mod help;
|
||||
pub mod init;
|
||||
pub mod resolve;
|
||||
pub mod server;
|
||||
pub mod tmpl;
|
||||
mod winhelp;
|
||||
// mod winhelp;
|
||||
|
@ -1,48 +1,43 @@
|
||||
use crate::{
|
||||
core::{tray, Core},
|
||||
data::Data,
|
||||
utils::init,
|
||||
utils::server,
|
||||
};
|
||||
use crate::config::Config;
|
||||
use crate::log_err;
|
||||
use crate::{core::*, utils::init, utils::server};
|
||||
use tauri::{App, AppHandle, Manager};
|
||||
|
||||
/// handle something when start app
|
||||
pub fn resolve_setup(app: &App) {
|
||||
let _ = app
|
||||
.tray_handle()
|
||||
.set_menu(tray::Tray::tray_menu(&app.app_handle()));
|
||||
pub fn resolve_setup(app: &mut App) {
|
||||
#[cfg(target_os = "macos")]
|
||||
app.set_activation_policy(tauri::ActivationPolicy::Accessory);
|
||||
|
||||
init::init_resources(app.package_info());
|
||||
handle::Handle::global().init(app.app_handle());
|
||||
|
||||
let silent_start = {
|
||||
let global = Data::global();
|
||||
let verge = global.verge.lock();
|
||||
let singleton = verge.app_singleton_port.clone();
|
||||
log_err!(init::init_resources(app.package_info()));
|
||||
|
||||
// 启动核心
|
||||
log_err!(Config::init_config());
|
||||
log_err!(CoreManager::global().init());
|
||||
|
||||
// setup a simple http server for singleton
|
||||
server::embed_server(&app.app_handle(), singleton);
|
||||
server::embed_server(app.app_handle());
|
||||
|
||||
verge.enable_silent_start.clone().unwrap_or(false)
|
||||
};
|
||||
log_err!(tray::Tray::update_systray(&app.app_handle()));
|
||||
|
||||
// core should be initialized after init_app fix #122
|
||||
let core = Core::global();
|
||||
core.init(app.app_handle());
|
||||
|
||||
if !silent_start {
|
||||
let silent_start = { Config::verge().data().enable_silent_start.clone() };
|
||||
if !silent_start.unwrap_or(false) {
|
||||
create_window(&app.app_handle());
|
||||
}
|
||||
|
||||
log_err!(sysopt::Sysopt::global().init_launch());
|
||||
log_err!(sysopt::Sysopt::global().init_sysproxy());
|
||||
|
||||
log_err!(handle::Handle::update_systray_part());
|
||||
log_err!(hotkey::Hotkey::global().init(app.app_handle()));
|
||||
log_err!(timer::Timer::global().init());
|
||||
}
|
||||
|
||||
/// reset system proxy
|
||||
pub fn resolve_reset() {
|
||||
let core = Core::global();
|
||||
let mut sysopt = core.sysopt.lock();
|
||||
crate::log_if_err!(sysopt.reset_sysproxy());
|
||||
drop(sysopt);
|
||||
|
||||
let mut service = core.service.lock();
|
||||
crate::log_if_err!(service.stop());
|
||||
log_err!(sysopt::Sysopt::global().reset_sysproxy());
|
||||
log_err!(CoreManager::global().stop_core());
|
||||
}
|
||||
|
||||
/// create main window
|
||||
@ -66,31 +61,31 @@ pub fn create_window(app_handle: &AppHandle) {
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use crate::utils::winhelp;
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
use window_shadows::set_shadow;
|
||||
use window_vibrancy::apply_blur;
|
||||
|
||||
match builder
|
||||
.decorations(false)
|
||||
.transparent(true)
|
||||
.inner_size(800.0, 636.0)
|
||||
.visible(false)
|
||||
.build()
|
||||
{
|
||||
Ok(_) => {
|
||||
let app_handle = app_handle.clone();
|
||||
|
||||
if let Some(window) = app_handle.get_window("main") {
|
||||
let _ = set_shadow(&window, true);
|
||||
}
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
|
||||
if let Some(window) = app_handle.get_window("main") {
|
||||
let _ = window.show();
|
||||
let _ = set_shadow(&window, true);
|
||||
|
||||
if !winhelp::is_win11() {
|
||||
let _ = apply_blur(&window, None);
|
||||
}
|
||||
let _ = window.unminimize();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -99,10 +94,10 @@ pub fn create_window(app_handle: &AppHandle) {
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
crate::log_if_err!(builder.decorations(true).inner_size(800.0, 642.0).build());
|
||||
crate::log_err!(builder.decorations(true).inner_size(800.0, 642.0).build());
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
crate::log_if_err!(builder
|
||||
crate::log_err!(builder
|
||||
.decorations(false)
|
||||
.transparent(true)
|
||||
.inner_size(800.0, 636.0)
|
||||
|
@ -1,26 +1,27 @@
|
||||
extern crate warp;
|
||||
|
||||
use super::resolve;
|
||||
use crate::data::Verge;
|
||||
use crate::config::IVerge;
|
||||
use anyhow::{bail, Result};
|
||||
use port_scanner::local_port_available;
|
||||
use tauri::AppHandle;
|
||||
use warp::Filter;
|
||||
|
||||
#[cfg(not(feature = "verge-dev"))]
|
||||
const SERVER_PORT: u16 = 33331;
|
||||
#[cfg(feature = "verge-dev")]
|
||||
const SERVER_PORT: u16 = 11233;
|
||||
|
||||
/// check whether there is already exists
|
||||
pub fn check_singleton() -> Result<(), ()> {
|
||||
let verge = Verge::new();
|
||||
let port = verge.app_singleton_port.unwrap_or(SERVER_PORT);
|
||||
pub fn check_singleton() -> Result<()> {
|
||||
let port = IVerge::get_singleton_port();
|
||||
|
||||
if !local_port_available(port) {
|
||||
tauri::async_runtime::block_on(async {
|
||||
let url = format!("http://127.0.0.1:{}/commands/visible", port);
|
||||
reqwest::get(url).await.unwrap();
|
||||
Err(())
|
||||
let url = format!("http://127.0.0.1:{port}/commands/visible");
|
||||
let resp = reqwest::get(url).await?.text().await?;
|
||||
|
||||
if &resp == "ok" {
|
||||
bail!("app exists");
|
||||
}
|
||||
|
||||
log::error!("failed to setup singleton listen server");
|
||||
Ok(())
|
||||
})
|
||||
} else {
|
||||
Ok(())
|
||||
@ -29,14 +30,13 @@ pub fn check_singleton() -> Result<(), ()> {
|
||||
|
||||
/// The embed server only be used to implement singleton process
|
||||
/// maybe it can be used as pac server later
|
||||
pub fn embed_server(app_handle: &AppHandle, port: Option<u16>) {
|
||||
let app_handle = app_handle.clone();
|
||||
let port = port.unwrap_or(SERVER_PORT);
|
||||
pub fn embed_server(app_handle: AppHandle) {
|
||||
let port = IVerge::get_singleton_port();
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let commands = warp::path!("commands" / "visible").map(move || {
|
||||
resolve::create_window(&app_handle);
|
||||
return format!("ok");
|
||||
format!("ok")
|
||||
});
|
||||
|
||||
warp::serve(commands).bind(([127, 0, 0, 1], port)).await;
|
||||
|
@ -1,37 +1,5 @@
|
||||
///! Some config file template
|
||||
|
||||
/// template for clash core `config.yaml`
|
||||
pub const CLASH_CONFIG: &[u8] = br#"# Default Config For Clash Core
|
||||
|
||||
mixed-port: 7890
|
||||
log-level: info
|
||||
allow-lan: false
|
||||
external-controller: 127.0.0.1:9090
|
||||
mode: rule
|
||||
secret: ""
|
||||
"#;
|
||||
|
||||
/// template for `profiles.yaml`
|
||||
pub const PROFILES_CONFIG: &[u8] = b"# Profiles Config for Clash Verge
|
||||
|
||||
current: ~
|
||||
items: ~
|
||||
";
|
||||
|
||||
/// template for `verge.yaml`
|
||||
pub const VERGE_CONFIG: &[u8] = b"# Defaulf Config For Clash Verge
|
||||
|
||||
clash_core: clash-meta
|
||||
language: en
|
||||
theme_mode: light
|
||||
theme_blur: false
|
||||
traffic_graph: true
|
||||
enable_self_startup: false
|
||||
enable_system_proxy: false
|
||||
enable_proxy_guard: false
|
||||
proxy_guard_duration: 10
|
||||
";
|
||||
|
||||
/// template for new a profile item
|
||||
pub const ITEM_LOCAL: &str = "# Profile Template for clash verge
|
||||
|
||||
@ -61,9 +29,7 @@ append-proxy-groups:
|
||||
|
||||
/// enhanced profile
|
||||
pub const ITEM_SCRIPT: &str = "// Define the `main` function
|
||||
// The argument to this function is the clash config
|
||||
// or the result of the previous handler
|
||||
// so you should return the config after processing
|
||||
|
||||
function main(params) {
|
||||
return params;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
getProfiles,
|
||||
deleteProfile,
|
||||
enhanceProfiles,
|
||||
changeProfileChain,
|
||||
patchProfilesConfig,
|
||||
getRuntimeLogs,
|
||||
} from "@/services/cmds";
|
||||
import ProfileMore from "./profile-more";
|
||||
@ -43,7 +43,7 @@ const EnhancedMode = (props: Props) => {
|
||||
if (chain.includes(uid)) return;
|
||||
|
||||
const newChain = [...chain, uid];
|
||||
await changeProfileChain(newChain);
|
||||
await patchProfilesConfig({ chain: newChain });
|
||||
mutateProfiles((conf = {}) => ({ ...conf, chain: newChain }), true);
|
||||
mutateLogs();
|
||||
});
|
||||
@ -52,7 +52,7 @@ const EnhancedMode = (props: Props) => {
|
||||
if (!chain.includes(uid)) return;
|
||||
|
||||
const newChain = chain.filter((i) => i !== uid);
|
||||
await changeProfileChain(newChain);
|
||||
await patchProfilesConfig({ chain: newChain });
|
||||
mutateProfiles((conf = {}) => ({ ...conf, chain: newChain }), true);
|
||||
mutateLogs();
|
||||
});
|
||||
@ -72,7 +72,7 @@ const EnhancedMode = (props: Props) => {
|
||||
if (!chain.includes(uid)) return;
|
||||
|
||||
const newChain = [uid].concat(chain.filter((i) => i !== uid));
|
||||
await changeProfileChain(newChain);
|
||||
await patchProfilesConfig({ chain: newChain });
|
||||
mutateProfiles((conf = {}) => ({ ...conf, chain: newChain }), true);
|
||||
mutateLogs();
|
||||
});
|
||||
@ -81,7 +81,7 @@ const EnhancedMode = (props: Props) => {
|
||||
if (!chain.includes(uid)) return;
|
||||
|
||||
const newChain = chain.filter((i) => i !== uid).concat([uid]);
|
||||
await changeProfileChain(newChain);
|
||||
await patchProfilesConfig({ chain: newChain });
|
||||
mutateProfiles((conf = {}) => ({ ...conf, chain: newChain }), true);
|
||||
mutateLogs();
|
||||
});
|
||||
|
@ -15,9 +15,9 @@ import {
|
||||
} from "@mui/material";
|
||||
import { InfoRounded } from "@mui/icons-material";
|
||||
import {
|
||||
changeProfileValid,
|
||||
getProfiles,
|
||||
getRuntimeExists,
|
||||
patchProfilesConfig,
|
||||
} from "@/services/cmds";
|
||||
import { ModalHandler } from "@/hooks/use-modal-handler";
|
||||
import {
|
||||
@ -91,7 +91,7 @@ const ClashFieldViewer = ({ handler }: Props) => {
|
||||
if (curSet.size === oldSet.size && curSet.size === joinSet.size) return;
|
||||
|
||||
try {
|
||||
await changeProfileValid([...curSet]);
|
||||
await patchProfilesConfig({ valid: [...curSet] });
|
||||
mutateProfile();
|
||||
// Notice.success("Refresh clash config", 1000);
|
||||
} catch (err: any) {
|
||||
|
@ -7,7 +7,7 @@ import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
getProfiles,
|
||||
patchProfile,
|
||||
selectProfile,
|
||||
patchProfilesConfig,
|
||||
importProfile,
|
||||
} from "@/services/cmds";
|
||||
import { getProxies, updateProxy } from "@/services/api";
|
||||
@ -113,7 +113,7 @@ const ProfilePage = () => {
|
||||
|
||||
if (!newProfiles.current && remoteItem) {
|
||||
const current = remoteItem.uid;
|
||||
selectProfile(current);
|
||||
patchProfilesConfig({ current });
|
||||
mutate("getProfiles", { ...newProfiles, current }, true);
|
||||
mutate("getRuntimeLogs");
|
||||
}
|
||||
@ -125,13 +125,13 @@ const ProfilePage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const onSelect = useLockFn(async (uid: string, force: boolean) => {
|
||||
if (!force && uid === profiles.current) return;
|
||||
const onSelect = useLockFn(async (current: string, force: boolean) => {
|
||||
if (!force && current === profiles.current) return;
|
||||
|
||||
try {
|
||||
await selectProfile(uid);
|
||||
setCurrentProfile(uid);
|
||||
mutate("getProfiles", { ...profiles, current: uid }, true);
|
||||
await patchProfilesConfig({ current });
|
||||
setCurrentProfile(current);
|
||||
mutate("getProfiles", { ...profiles, current: current }, true);
|
||||
mutate("getRuntimeLogs");
|
||||
// if (force) Notice.success("Refresh clash config", 1000);
|
||||
} catch (err: any) {
|
||||
|
@ -32,6 +32,10 @@ export async function enhanceProfiles() {
|
||||
return invoke<void>("enhance_profiles");
|
||||
}
|
||||
|
||||
export async function patchProfilesConfig(profiles: CmdType.ProfilesConfig) {
|
||||
return invoke<void>("patch_profiles_config");
|
||||
}
|
||||
|
||||
export async function createProfile(
|
||||
item: Partial<CmdType.ProfileItem>,
|
||||
fileData?: string | null
|
||||
@ -76,18 +80,6 @@ export async function patchProfile(
|
||||
return invoke<void>("patch_profile", { index, profile });
|
||||
}
|
||||
|
||||
export async function selectProfile(index: string) {
|
||||
return invoke<void>("select_profile", { index });
|
||||
}
|
||||
|
||||
export async function changeProfileChain(chain?: string[]) {
|
||||
return invoke<void>("change_profile_chain", { chain });
|
||||
}
|
||||
|
||||
export async function changeProfileValid(valid?: string[]) {
|
||||
return invoke<void>("change_profile_valid", { valid });
|
||||
}
|
||||
|
||||
export async function getClashInfo() {
|
||||
return invoke<CmdType.ClashInfo | null>("get_clash_info");
|
||||
}
|
||||
@ -136,10 +128,6 @@ export async function restartSidecar() {
|
||||
return invoke<void>("restart_sidecar");
|
||||
}
|
||||
|
||||
export async function killSidecar() {
|
||||
return invoke<any>("kill_sidecar");
|
||||
}
|
||||
|
||||
export async function openAppDir() {
|
||||
return invoke<void>("open_app_dir").catch((err) =>
|
||||
Notice.error(err?.message || err.toString(), 1500)
|
||||
@ -158,14 +146,6 @@ export async function openWebUrl(url: string) {
|
||||
|
||||
/// service mode
|
||||
|
||||
export async function startService() {
|
||||
return invoke<void>("start_service");
|
||||
}
|
||||
|
||||
export async function stopService() {
|
||||
return invoke<void>("stop_service");
|
||||
}
|
||||
|
||||
export async function checkService() {
|
||||
try {
|
||||
const result = await invoke<any>("check_service");
|
||||
|
Loading…
Reference in New Issue
Block a user