mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2024-11-16 03:32:36 +08:00
chore: format rust code
This commit is contained in:
parent
a211fc7c97
commit
4668be6e24
|
@ -5,3 +5,9 @@ charset = utf-8
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.rs]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 4
|
||||||
|
insert_final_newline = true
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
fn main() {
|
fn main() {
|
||||||
tauri_build::build()
|
tauri_build::build()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
max_width = 100
|
max_width = 100
|
||||||
hard_tabs = false
|
hard_tabs = false
|
||||||
tab_spaces = 2
|
tab_spaces = 4
|
||||||
newline_style = "Auto"
|
newline_style = "Auto"
|
||||||
use_small_heuristics = "Default"
|
use_small_heuristics = "Default"
|
||||||
reorder_imports = true
|
reorder_imports = true
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
core::Core,
|
core::Core,
|
||||||
data::{ClashInfo, Data, PrfItem, PrfOption, Profiles, Verge},
|
data::{ClashInfo, Data, PrfItem, PrfOption, Profiles, Verge},
|
||||||
utils::{dirs, help},
|
utils::{dirs, help},
|
||||||
};
|
};
|
||||||
use crate::{log_if_err, ret_err, wrap_err};
|
use crate::{log_if_err, ret_err, wrap_err};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
@ -14,27 +14,27 @@ type CmdResult<T = ()> = Result<T, String>;
|
||||||
/// get all profiles from `profiles.yaml`
|
/// get all profiles from `profiles.yaml`
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_profiles() -> CmdResult<Profiles> {
|
pub fn get_profiles() -> CmdResult<Profiles> {
|
||||||
let global = Data::global();
|
let global = Data::global();
|
||||||
let profiles = global.profiles.lock();
|
let profiles = global.profiles.lock();
|
||||||
Ok(profiles.clone())
|
Ok(profiles.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// manually exec enhanced profile
|
/// manually exec enhanced profile
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn enhance_profiles() -> CmdResult {
|
pub fn enhance_profiles() -> CmdResult {
|
||||||
let core = Core::global();
|
let core = Core::global();
|
||||||
wrap_err!(core.activate())
|
wrap_err!(core.activate())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// import the profile from url
|
/// import the profile from url
|
||||||
/// and save to `profiles.yaml`
|
/// and save to `profiles.yaml`
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn import_profile(url: String, option: Option<PrfOption>) -> CmdResult {
|
pub async fn import_profile(url: String, option: Option<PrfOption>) -> CmdResult {
|
||||||
let item = wrap_err!(PrfItem::from_url(&url, None, None, option).await)?;
|
let item = wrap_err!(PrfItem::from_url(&url, None, None, option).await)?;
|
||||||
|
|
||||||
let global = Data::global();
|
let global = Data::global();
|
||||||
let mut profiles = global.profiles.lock();
|
let mut profiles = global.profiles.lock();
|
||||||
wrap_err!(profiles.append_item(item))
|
wrap_err!(profiles.append_item(item))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// new a profile
|
/// new a profile
|
||||||
|
@ -42,166 +42,166 @@ pub async fn import_profile(url: String, option: Option<PrfOption>) -> CmdResult
|
||||||
/// view the temp profile file by using vscode or other editor
|
/// view the temp profile file by using vscode or other editor
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResult {
|
pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResult {
|
||||||
let item = wrap_err!(PrfItem::from(item, file_data).await)?;
|
let item = wrap_err!(PrfItem::from(item, file_data).await)?;
|
||||||
|
|
||||||
let global = Data::global();
|
let global = Data::global();
|
||||||
let mut profiles = global.profiles.lock();
|
let mut profiles = global.profiles.lock();
|
||||||
wrap_err!(profiles.append_item(item))
|
wrap_err!(profiles.append_item(item))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the profile
|
/// Update the profile
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn update_profile(index: String, option: Option<PrfOption>) -> CmdResult {
|
pub async fn update_profile(index: String, option: Option<PrfOption>) -> CmdResult {
|
||||||
let core = Core::global();
|
let core = Core::global();
|
||||||
wrap_err!(core.update_profile_item(index, option).await)
|
wrap_err!(core.update_profile_item(index, option).await)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// change the current profile
|
/// change the current profile
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn select_profile(index: String) -> CmdResult {
|
pub fn select_profile(index: String) -> CmdResult {
|
||||||
let global = Data::global();
|
let global = Data::global();
|
||||||
let mut profiles = global.profiles.lock();
|
let mut profiles = global.profiles.lock();
|
||||||
wrap_err!(profiles.put_current(index))?;
|
wrap_err!(profiles.put_current(index))?;
|
||||||
drop(profiles);
|
drop(profiles);
|
||||||
|
|
||||||
let core = Core::global();
|
let core = Core::global();
|
||||||
wrap_err!(core.activate())
|
wrap_err!(core.activate())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// change the profile chain
|
/// change the profile chain
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn change_profile_chain(chain: Option<Vec<String>>) -> CmdResult {
|
pub fn change_profile_chain(chain: Option<Vec<String>>) -> CmdResult {
|
||||||
let global = Data::global();
|
let global = Data::global();
|
||||||
let mut profiles = global.profiles.lock();
|
let mut profiles = global.profiles.lock();
|
||||||
wrap_err!(profiles.put_chain(chain))?;
|
wrap_err!(profiles.put_chain(chain))?;
|
||||||
drop(profiles);
|
drop(profiles);
|
||||||
|
|
||||||
let core = Core::global();
|
let core = Core::global();
|
||||||
wrap_err!(core.activate())
|
wrap_err!(core.activate())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// change the profile valid fields
|
/// change the profile valid fields
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn change_profile_valid(valid: Option<Vec<String>>) -> CmdResult {
|
pub fn change_profile_valid(valid: Option<Vec<String>>) -> CmdResult {
|
||||||
let global = Data::global();
|
let global = Data::global();
|
||||||
let mut profiles = global.profiles.lock();
|
let mut profiles = global.profiles.lock();
|
||||||
wrap_err!(profiles.put_valid(valid))?;
|
wrap_err!(profiles.put_valid(valid))?;
|
||||||
drop(profiles);
|
drop(profiles);
|
||||||
|
|
||||||
let core = Core::global();
|
let core = Core::global();
|
||||||
wrap_err!(core.activate())
|
wrap_err!(core.activate())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// delete profile item
|
/// delete profile item
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn delete_profile(index: String) -> CmdResult {
|
pub fn delete_profile(index: String) -> CmdResult {
|
||||||
let global = Data::global();
|
let global = Data::global();
|
||||||
let mut profiles = global.profiles.lock();
|
let mut profiles = global.profiles.lock();
|
||||||
if wrap_err!(profiles.delete_item(index))? {
|
if wrap_err!(profiles.delete_item(index))? {
|
||||||
drop(profiles);
|
drop(profiles);
|
||||||
|
|
||||||
let core = Core::global();
|
let core = Core::global();
|
||||||
log_if_err!(core.activate());
|
log_if_err!(core.activate());
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// patch the profile config
|
/// patch the profile config
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn patch_profile(index: String, profile: PrfItem) -> CmdResult {
|
pub fn patch_profile(index: String, profile: PrfItem) -> CmdResult {
|
||||||
let global = Data::global();
|
let global = Data::global();
|
||||||
let mut profiles = global.profiles.lock();
|
let mut profiles = global.profiles.lock();
|
||||||
wrap_err!(profiles.patch_item(index, profile))?;
|
wrap_err!(profiles.patch_item(index, profile))?;
|
||||||
drop(profiles);
|
drop(profiles);
|
||||||
|
|
||||||
// update cron task
|
// update cron task
|
||||||
let core = Core::global();
|
let core = Core::global();
|
||||||
let mut timer = core.timer.lock();
|
let mut timer = core.timer.lock();
|
||||||
wrap_err!(timer.refresh())
|
wrap_err!(timer.refresh())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// run vscode command to edit the profile
|
/// run vscode command to edit the profile
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn view_profile(index: String) -> CmdResult {
|
pub fn view_profile(index: String) -> CmdResult {
|
||||||
let global = Data::global();
|
let global = Data::global();
|
||||||
let profiles = global.profiles.lock();
|
let profiles = global.profiles.lock();
|
||||||
let item = wrap_err!(profiles.get_item(&index))?;
|
let item = wrap_err!(profiles.get_item(&index))?;
|
||||||
|
|
||||||
let file = item.file.clone();
|
let file = item.file.clone();
|
||||||
if file.is_none() {
|
if file.is_none() {
|
||||||
ret_err!("file is null");
|
ret_err!("file is null");
|
||||||
}
|
}
|
||||||
|
|
||||||
let path = dirs::app_profiles_dir().join(file.unwrap());
|
let path = dirs::app_profiles_dir().join(file.unwrap());
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
ret_err!("file not found");
|
ret_err!("file not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
wrap_err!(help::open_file(path))
|
wrap_err!(help::open_file(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// read the profile item file data
|
/// read the profile item file data
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn read_profile_file(index: String) -> CmdResult<String> {
|
pub fn read_profile_file(index: String) -> CmdResult<String> {
|
||||||
let global = Data::global();
|
let global = Data::global();
|
||||||
let profiles = global.profiles.lock();
|
let profiles = global.profiles.lock();
|
||||||
let item = wrap_err!(profiles.get_item(&index))?;
|
let item = wrap_err!(profiles.get_item(&index))?;
|
||||||
let data = wrap_err!(item.read_file())?;
|
let data = wrap_err!(item.read_file())?;
|
||||||
Ok(data)
|
Ok(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// save the profile item file data
|
/// save the profile item file data
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn save_profile_file(index: String, file_data: Option<String>) -> CmdResult {
|
pub fn save_profile_file(index: String, file_data: Option<String>) -> CmdResult {
|
||||||
if file_data.is_none() {
|
if file_data.is_none() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let global = Data::global();
|
let global = Data::global();
|
||||||
let profiles = global.profiles.lock();
|
let profiles = global.profiles.lock();
|
||||||
let item = wrap_err!(profiles.get_item(&index))?;
|
let item = wrap_err!(profiles.get_item(&index))?;
|
||||||
wrap_err!(item.save_file(file_data.unwrap()))
|
wrap_err!(item.save_file(file_data.unwrap()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get the clash core info from the state
|
/// get the clash core info from the state
|
||||||
/// the caller can also get the infomation by clash's api
|
/// the caller can also get the infomation by clash's api
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_clash_info() -> CmdResult<ClashInfo> {
|
pub fn get_clash_info() -> CmdResult<ClashInfo> {
|
||||||
let global = Data::global();
|
let global = Data::global();
|
||||||
let clash = global.clash.lock();
|
let clash = global.clash.lock();
|
||||||
Ok(clash.info.clone())
|
Ok(clash.info.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get the runtime clash config mapping
|
/// get the runtime clash config mapping
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_runtime_config() -> CmdResult<Option<Mapping>> {
|
pub fn get_runtime_config() -> CmdResult<Option<Mapping>> {
|
||||||
let core = Core::global();
|
let core = Core::global();
|
||||||
let rt = core.runtime.lock();
|
let rt = core.runtime.lock();
|
||||||
Ok(rt.config.clone())
|
Ok(rt.config.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get the runtime clash config yaml string
|
/// get the runtime clash config yaml string
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_runtime_yaml() -> CmdResult<Option<String>> {
|
pub fn get_runtime_yaml() -> CmdResult<Option<String>> {
|
||||||
let core = Core::global();
|
let core = Core::global();
|
||||||
let rt = core.runtime.lock();
|
let rt = core.runtime.lock();
|
||||||
Ok(rt.config_yaml.clone())
|
Ok(rt.config_yaml.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get the runtime config exists keys
|
/// get the runtime config exists keys
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_runtime_exists() -> CmdResult<Vec<String>> {
|
pub fn get_runtime_exists() -> CmdResult<Vec<String>> {
|
||||||
let core = Core::global();
|
let core = Core::global();
|
||||||
let rt = core.runtime.lock();
|
let rt = core.runtime.lock();
|
||||||
Ok(rt.exists_keys.clone())
|
Ok(rt.exists_keys.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get the runtime enhanced chain log
|
/// get the runtime enhanced chain log
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_runtime_logs() -> CmdResult<HashMap<String, Vec<(String, String)>>> {
|
pub fn get_runtime_logs() -> CmdResult<HashMap<String, Vec<(String, String)>>> {
|
||||||
let core = Core::global();
|
let core = Core::global();
|
||||||
let rt = core.runtime.lock();
|
let rt = core.runtime.lock();
|
||||||
Ok(rt.chain_logs.clone())
|
Ok(rt.chain_logs.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// update the clash core config
|
/// update the clash core config
|
||||||
|
@ -209,153 +209,153 @@ pub fn get_runtime_logs() -> CmdResult<HashMap<String, Vec<(String, String)>>> {
|
||||||
/// then we should save the latest config
|
/// then we should save the latest config
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn patch_clash_config(payload: Mapping) -> CmdResult {
|
pub fn patch_clash_config(payload: Mapping) -> CmdResult {
|
||||||
let core = Core::global();
|
let core = Core::global();
|
||||||
wrap_err!(core.patch_clash(payload))
|
wrap_err!(core.patch_clash(payload))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_verge_config() -> CmdResult<Verge> {
|
pub fn get_verge_config() -> CmdResult<Verge> {
|
||||||
let global = Data::global();
|
let global = Data::global();
|
||||||
let verge = global.verge.lock();
|
let verge = global.verge.lock();
|
||||||
Ok(verge.clone())
|
Ok(verge.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// patch the verge config
|
/// patch the verge config
|
||||||
/// this command only save the config and not responsible for other things
|
/// this command only save the config and not responsible for other things
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn patch_verge_config(payload: Verge) -> CmdResult {
|
pub fn patch_verge_config(payload: Verge) -> CmdResult {
|
||||||
let core = Core::global();
|
let core = Core::global();
|
||||||
wrap_err!(core.patch_verge(payload))
|
wrap_err!(core.patch_verge(payload))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn update_hotkeys(hotkeys: Vec<String>) -> CmdResult {
|
pub fn update_hotkeys(hotkeys: Vec<String>) -> CmdResult {
|
||||||
let core = Core::global();
|
let core = Core::global();
|
||||||
let mut hotkey = core.hotkey.lock();
|
let mut hotkey = core.hotkey.lock();
|
||||||
wrap_err!(hotkey.update(hotkeys))
|
wrap_err!(hotkey.update(hotkeys))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// change clash core
|
/// change clash core
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn change_clash_core(clash_core: Option<String>) -> CmdResult {
|
pub fn change_clash_core(clash_core: Option<String>) -> CmdResult {
|
||||||
let core = Core::global();
|
let core = Core::global();
|
||||||
wrap_err!(core.change_core(clash_core))
|
wrap_err!(core.change_core(clash_core))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// restart the sidecar
|
/// restart the sidecar
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn restart_sidecar() -> CmdResult {
|
pub fn restart_sidecar() -> CmdResult {
|
||||||
let core = Core::global();
|
let core = Core::global();
|
||||||
wrap_err!(core.restart_clash())
|
wrap_err!(core.restart_clash())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// kill all sidecars when update app
|
/// kill all sidecars when update app
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn kill_sidecar() {
|
pub fn kill_sidecar() {
|
||||||
tauri::api::process::kill_children();
|
tauri::api::process::kill_children();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get the system proxy
|
/// get the system proxy
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_sys_proxy() -> CmdResult<Mapping> {
|
pub fn get_sys_proxy() -> CmdResult<Mapping> {
|
||||||
let current = wrap_err!(Sysproxy::get_system_proxy())?;
|
let current = wrap_err!(Sysproxy::get_system_proxy())?;
|
||||||
|
|
||||||
let mut map = Mapping::new();
|
let mut map = Mapping::new();
|
||||||
map.insert("enable".into(), current.enable.into());
|
map.insert("enable".into(), current.enable.into());
|
||||||
map.insert(
|
map.insert(
|
||||||
"server".into(),
|
"server".into(),
|
||||||
format!("{}:{}", current.host, current.port).into(),
|
format!("{}:{}", current.host, current.port).into(),
|
||||||
);
|
);
|
||||||
map.insert("bypass".into(), current.bypass.into());
|
map.insert("bypass".into(), current.bypass.into());
|
||||||
|
|
||||||
Ok(map)
|
Ok(map)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_clash_logs() -> CmdResult<VecDeque<String>> {
|
pub fn get_clash_logs() -> CmdResult<VecDeque<String>> {
|
||||||
let core = Core::global();
|
let core = Core::global();
|
||||||
let service = core.service.lock();
|
let service = core.service.lock();
|
||||||
Ok(service.get_logs())
|
Ok(service.get_logs())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// open app config dir
|
/// open app config dir
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn open_app_dir() -> CmdResult<()> {
|
pub fn open_app_dir() -> CmdResult<()> {
|
||||||
let app_dir = dirs::app_home_dir();
|
let app_dir = dirs::app_home_dir();
|
||||||
wrap_err!(open::that(app_dir))
|
wrap_err!(open::that(app_dir))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// open logs dir
|
/// open logs dir
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn open_logs_dir() -> CmdResult<()> {
|
pub fn open_logs_dir() -> CmdResult<()> {
|
||||||
let log_dir = dirs::app_logs_dir();
|
let log_dir = dirs::app_logs_dir();
|
||||||
wrap_err!(open::that(log_dir))
|
wrap_err!(open::that(log_dir))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// open url
|
/// open url
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn open_web_url(url: String) -> CmdResult<()> {
|
pub fn open_web_url(url: String) -> CmdResult<()> {
|
||||||
wrap_err!(open::that(url))
|
wrap_err!(open::that(url))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// service mode
|
/// service mode
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub mod service {
|
pub mod service {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::core::win_service::JsonResponse;
|
use crate::core::win_service::JsonResponse;
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn start_service() -> CmdResult<()> {
|
pub async fn start_service() -> CmdResult<()> {
|
||||||
wrap_err!(crate::core::Service::start_service().await)
|
wrap_err!(crate::core::Service::start_service().await)
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn stop_service() -> CmdResult<()> {
|
|
||||||
wrap_err!(crate::core::Service::stop_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]
|
#[tauri::command]
|
||||||
pub async fn install_service() -> CmdResult<()> {
|
pub async fn stop_service() -> CmdResult<()> {
|
||||||
wrap_err!(crate::core::Service::install_service().await)
|
wrap_err!(crate::core::Service::stop_service().await)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn uninstall_service() -> CmdResult<()> {
|
pub async fn check_service() -> CmdResult<JsonResponse> {
|
||||||
wrap_err!(crate::core::Service::uninstall_service().await)
|
// 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
pub mod service {
|
pub mod service {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn start_service() -> CmdResult<()> {
|
pub async fn start_service() -> CmdResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn stop_service() -> CmdResult<()> {
|
pub async fn stop_service() -> CmdResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn check_service() -> CmdResult<()> {
|
pub async fn check_service() -> CmdResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn install_service() -> CmdResult<()> {
|
pub async fn install_service() -> CmdResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn uninstall_service() -> CmdResult<()> {
|
pub async fn uninstall_service() -> CmdResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,119 +1,119 @@
|
||||||
use serde_yaml::{Mapping, Value};
|
use serde_yaml::{Mapping, Value};
|
||||||
|
|
||||||
pub const HANDLE_FIELDS: [&str; 9] = [
|
pub const HANDLE_FIELDS: [&str; 9] = [
|
||||||
"port",
|
"port",
|
||||||
"socks-port",
|
"socks-port",
|
||||||
"mixed-port",
|
"mixed-port",
|
||||||
"mode",
|
"mode",
|
||||||
"ipv6",
|
"ipv6",
|
||||||
"log-level",
|
"log-level",
|
||||||
"allow-lan",
|
"allow-lan",
|
||||||
"external-controller",
|
"external-controller",
|
||||||
"secret",
|
"secret",
|
||||||
];
|
];
|
||||||
|
|
||||||
pub const DEFAULT_FIELDS: [&str; 5] = [
|
pub const DEFAULT_FIELDS: [&str; 5] = [
|
||||||
"proxies",
|
"proxies",
|
||||||
"proxy-groups",
|
"proxy-groups",
|
||||||
"rules",
|
"rules",
|
||||||
"proxy-providers",
|
"proxy-providers",
|
||||||
"rule-providers",
|
"rule-providers",
|
||||||
];
|
];
|
||||||
|
|
||||||
pub const OTHERS_FIELDS: [&str; 21] = [
|
pub const OTHERS_FIELDS: [&str; 21] = [
|
||||||
"tun",
|
"tun",
|
||||||
"dns",
|
"dns",
|
||||||
"ebpf",
|
"ebpf",
|
||||||
"hosts",
|
"hosts",
|
||||||
"script",
|
"script",
|
||||||
"profile",
|
"profile",
|
||||||
"payload",
|
"payload",
|
||||||
"auto-redir",
|
"auto-redir",
|
||||||
"experimental",
|
"experimental",
|
||||||
"interface-name",
|
"interface-name",
|
||||||
"routing-mark",
|
"routing-mark",
|
||||||
"redir-port",
|
"redir-port",
|
||||||
"tproxy-port",
|
"tproxy-port",
|
||||||
"iptables",
|
"iptables",
|
||||||
"external-ui",
|
"external-ui",
|
||||||
"bind-address",
|
"bind-address",
|
||||||
"authentication",
|
"authentication",
|
||||||
"sniffer", // meta
|
"sniffer", // meta
|
||||||
"sub-rules", // meta
|
"sub-rules", // meta
|
||||||
"geodata-mode", // meta
|
"geodata-mode", // meta
|
||||||
"tcp-concurrent", // meta
|
"tcp-concurrent", // meta
|
||||||
];
|
];
|
||||||
|
|
||||||
pub fn use_clash_fields() -> Vec<String> {
|
pub fn use_clash_fields() -> Vec<String> {
|
||||||
DEFAULT_FIELDS
|
DEFAULT_FIELDS
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(HANDLE_FIELDS)
|
.chain(HANDLE_FIELDS)
|
||||||
.chain(OTHERS_FIELDS)
|
.chain(OTHERS_FIELDS)
|
||||||
.map(|s| s.to_string())
|
.map(|s| s.to_string())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn use_valid_fields(mut valid: Vec<String>) -> Vec<String> {
|
pub fn use_valid_fields(mut valid: Vec<String>) -> Vec<String> {
|
||||||
let others = Vec::from(OTHERS_FIELDS);
|
let others = Vec::from(OTHERS_FIELDS);
|
||||||
|
|
||||||
valid.iter_mut().for_each(|s| s.make_ascii_lowercase());
|
valid.iter_mut().for_each(|s| s.make_ascii_lowercase());
|
||||||
valid
|
valid
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|s| others.contains(&s.as_str()))
|
.filter(|s| others.contains(&s.as_str()))
|
||||||
.chain(DEFAULT_FIELDS.iter().map(|s| s.to_string()))
|
.chain(DEFAULT_FIELDS.iter().map(|s| s.to_string()))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn use_filter(config: Mapping, filter: &Vec<String>) -> Mapping {
|
pub fn use_filter(config: Mapping, filter: &Vec<String>) -> Mapping {
|
||||||
let mut ret = Mapping::new();
|
let mut ret = Mapping::new();
|
||||||
|
|
||||||
for (key, value) in config.into_iter() {
|
for (key, value) in config.into_iter() {
|
||||||
if let Some(key) = key.as_str() {
|
if let Some(key) = key.as_str() {
|
||||||
if filter.contains(&key.to_string()) {
|
if filter.contains(&key.to_string()) {
|
||||||
ret.insert(Value::from(key), value);
|
ret.insert(Value::from(key), value);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
ret
|
||||||
ret
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn use_lowercase(config: Mapping) -> Mapping {
|
pub fn use_lowercase(config: Mapping) -> Mapping {
|
||||||
let mut ret = Mapping::new();
|
let mut ret = Mapping::new();
|
||||||
|
|
||||||
for (key, value) in config.into_iter() {
|
for (key, value) in config.into_iter() {
|
||||||
if let Some(key_str) = key.as_str() {
|
if let Some(key_str) = key.as_str() {
|
||||||
let mut key_str = String::from(key_str);
|
let mut key_str = String::from(key_str);
|
||||||
key_str.make_ascii_lowercase();
|
key_str.make_ascii_lowercase();
|
||||||
ret.insert(Value::from(key_str), value);
|
ret.insert(Value::from(key_str), value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
ret
|
||||||
ret
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn use_sort(config: Mapping) -> Mapping {
|
pub fn use_sort(config: Mapping) -> Mapping {
|
||||||
let mut ret = Mapping::new();
|
let mut ret = Mapping::new();
|
||||||
|
|
||||||
HANDLE_FIELDS
|
HANDLE_FIELDS
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(OTHERS_FIELDS)
|
.chain(OTHERS_FIELDS)
|
||||||
.chain(DEFAULT_FIELDS)
|
.chain(DEFAULT_FIELDS)
|
||||||
.for_each(|key| {
|
.for_each(|key| {
|
||||||
let key = Value::from(key);
|
let key = Value::from(key);
|
||||||
config.get(&key).map(|value| {
|
config.get(&key).map(|value| {
|
||||||
ret.insert(key, value.clone());
|
ret.insert(key, value.clone());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn use_keys(config: &Mapping) -> Vec<String> {
|
pub fn use_keys(config: &Mapping) -> Vec<String> {
|
||||||
config
|
config
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(key, _)| key.as_str())
|
.filter_map(|(key, _)| key.as_str())
|
||||||
.map(|s| {
|
.map(|s| {
|
||||||
let mut s = s.to_string();
|
let mut s = s.to_string();
|
||||||
s.make_ascii_lowercase();
|
s.make_ascii_lowercase();
|
||||||
return s;
|
return s;
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,60 +3,60 @@ use serde_yaml::{self, Mapping, Sequence, Value};
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
const MERGE_FIELDS: [&str; 6] = [
|
const MERGE_FIELDS: [&str; 6] = [
|
||||||
"prepend-rules",
|
"prepend-rules",
|
||||||
"append-rules",
|
"append-rules",
|
||||||
"prepend-proxies",
|
"prepend-proxies",
|
||||||
"append-proxies",
|
"append-proxies",
|
||||||
"prepend-proxy-groups",
|
"prepend-proxy-groups",
|
||||||
"append-proxy-groups",
|
"append-proxy-groups",
|
||||||
];
|
];
|
||||||
|
|
||||||
pub fn use_merge(merge: Mapping, mut config: Mapping) -> Mapping {
|
pub fn use_merge(merge: Mapping, mut config: Mapping) -> Mapping {
|
||||||
// 直接覆盖原字段
|
// 直接覆盖原字段
|
||||||
use_lowercase(merge.clone())
|
use_lowercase(merge.clone())
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.for_each(|(key, value)| {
|
.for_each(|(key, value)| {
|
||||||
config.insert(key, value);
|
config.insert(key, value);
|
||||||
});
|
});
|
||||||
|
|
||||||
let merge_list = MERGE_FIELDS.iter().map(|s| s.to_string());
|
let merge_list = MERGE_FIELDS.iter().map(|s| s.to_string());
|
||||||
let merge = use_filter(merge, &merge_list.collect());
|
let merge = use_filter(merge, &merge_list.collect());
|
||||||
|
|
||||||
["rules", "proxies", "proxy-groups"]
|
["rules", "proxies", "proxy-groups"]
|
||||||
.iter()
|
.iter()
|
||||||
.for_each(|key_str| {
|
.for_each(|key_str| {
|
||||||
let key_val = Value::from(key_str.to_string());
|
let key_val = Value::from(key_str.to_string());
|
||||||
|
|
||||||
let mut list = Sequence::default();
|
let mut list = Sequence::default();
|
||||||
list = config.get(&key_val).map_or(list.clone(), |val| {
|
list = config.get(&key_val).map_or(list.clone(), |val| {
|
||||||
val.as_sequence().map_or(list, |v| v.clone())
|
val.as_sequence().map_or(list, |v| v.clone())
|
||||||
});
|
});
|
||||||
|
|
||||||
let pre_key = Value::from(format!("prepend-{key_str}"));
|
let pre_key = Value::from(format!("prepend-{key_str}"));
|
||||||
let post_key = Value::from(format!("append-{key_str}"));
|
let post_key = Value::from(format!("append-{key_str}"));
|
||||||
|
|
||||||
if let Some(pre_val) = merge.get(&pre_key) {
|
if let Some(pre_val) = merge.get(&pre_key) {
|
||||||
if pre_val.is_sequence() {
|
if pre_val.is_sequence() {
|
||||||
let mut pre_val = pre_val.as_sequence().unwrap().clone();
|
let mut pre_val = pre_val.as_sequence().unwrap().clone();
|
||||||
pre_val.extend(list);
|
pre_val.extend(list);
|
||||||
list = pre_val;
|
list = pre_val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(post_val) = merge.get(&post_key) {
|
if let Some(post_val) = merge.get(&post_key) {
|
||||||
if post_val.is_sequence() {
|
if post_val.is_sequence() {
|
||||||
list.extend(post_val.as_sequence().unwrap().clone());
|
list.extend(post_val.as_sequence().unwrap().clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
config.insert(key_val, Value::from(list));
|
config.insert(key_val, Value::from(list));
|
||||||
});
|
});
|
||||||
config
|
config
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_merge() -> anyhow::Result<()> {
|
fn test_merge() -> anyhow::Result<()> {
|
||||||
let merge = r"
|
let merge = r"
|
||||||
prepend-rules:
|
prepend-rules:
|
||||||
- prepend
|
- prepend
|
||||||
- 1123123
|
- 1123123
|
||||||
|
@ -76,18 +76,18 @@ fn test_merge() -> anyhow::Result<()> {
|
||||||
enable: true
|
enable: true
|
||||||
";
|
";
|
||||||
|
|
||||||
let config = r"
|
let config = r"
|
||||||
rules:
|
rules:
|
||||||
- aaaaa
|
- aaaaa
|
||||||
script1: test
|
script1: test
|
||||||
";
|
";
|
||||||
|
|
||||||
let merge = serde_yaml::from_str::<Mapping>(merge)?;
|
let merge = serde_yaml::from_str::<Mapping>(merge)?;
|
||||||
let config = serde_yaml::from_str::<Mapping>(config)?;
|
let config = serde_yaml::from_str::<Mapping>(config)?;
|
||||||
|
|
||||||
let result = serde_yaml::to_string(&use_merge(merge, config))?;
|
let result = serde_yaml::to_string(&use_merge(merge, config))?;
|
||||||
|
|
||||||
println!("{result}");
|
println!("{result}");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,54 +16,54 @@ use std::collections::HashSet;
|
||||||
type ResultLog = Vec<(String, String)>;
|
type ResultLog = Vec<(String, String)>;
|
||||||
|
|
||||||
pub fn enhance_config(
|
pub fn enhance_config(
|
||||||
clash_config: Mapping,
|
clash_config: Mapping,
|
||||||
profile_config: Mapping,
|
profile_config: Mapping,
|
||||||
chain: Vec<ChainItem>,
|
chain: Vec<ChainItem>,
|
||||||
valid: Vec<String>,
|
valid: Vec<String>,
|
||||||
tun_mode: bool,
|
tun_mode: bool,
|
||||||
) -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
) -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
||||||
let mut config = profile_config;
|
let mut config = profile_config;
|
||||||
let mut result_map = HashMap::new();
|
let mut result_map = HashMap::new();
|
||||||
let mut exists_keys = use_keys(&config);
|
let mut exists_keys = use_keys(&config);
|
||||||
|
|
||||||
let valid = use_valid_fields(valid);
|
let valid = use_valid_fields(valid);
|
||||||
|
|
||||||
chain.into_iter().for_each(|item| match item.data {
|
chain.into_iter().for_each(|item| match item.data {
|
||||||
ChainType::Merge(merge) => {
|
ChainType::Merge(merge) => {
|
||||||
exists_keys.extend(use_keys(&merge));
|
exists_keys.extend(use_keys(&merge));
|
||||||
config = use_merge(merge, config.to_owned());
|
config = use_merge(merge, config.to_owned());
|
||||||
config = use_filter(config.to_owned(), &valid);
|
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())),
|
ChainType::Script(script) => {
|
||||||
}
|
let mut logs = vec![];
|
||||||
|
|
||||||
result_map.insert(item.uid, logs);
|
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);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
config = use_filter(config, &valid);
|
let clash_fields = use_clash_fields();
|
||||||
|
config = use_filter(config, &clash_fields);
|
||||||
|
config = use_tun(config, tun_mode);
|
||||||
|
config = use_sort(config);
|
||||||
|
|
||||||
for (key, value) in clash_config.into_iter() {
|
let mut exists_set = HashSet::new();
|
||||||
config.insert(key, value);
|
exists_set.extend(exists_keys.into_iter().filter(|s| clash_fields.contains(s)));
|
||||||
}
|
exists_keys = exists_set.into_iter().collect();
|
||||||
|
|
||||||
let clash_fields = use_clash_fields();
|
(config, exists_keys, result_map)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,66 +3,66 @@ use anyhow::Result;
|
||||||
use serde_yaml::Mapping;
|
use serde_yaml::Mapping;
|
||||||
|
|
||||||
pub fn use_script(script: String, config: Mapping) -> Result<(Mapping, Vec<(String, String)>)> {
|
pub fn use_script(script: String, config: Mapping) -> Result<(Mapping, Vec<(String, String)>)> {
|
||||||
use rquickjs::{Context, Func, Runtime};
|
use rquickjs::{Context, Func, Runtime};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
let runtime = Runtime::new().unwrap();
|
let runtime = Runtime::new().unwrap();
|
||||||
let context = Context::full(&runtime).unwrap();
|
let context = Context::full(&runtime).unwrap();
|
||||||
let outputs = Arc::new(Mutex::new(vec![]));
|
let outputs = Arc::new(Mutex::new(vec![]));
|
||||||
|
|
||||||
let copy_outputs = outputs.clone();
|
let copy_outputs = outputs.clone();
|
||||||
let result = context.with(|ctx| -> Result<Mapping> {
|
let result = context.with(|ctx| -> Result<Mapping> {
|
||||||
ctx.globals().set(
|
ctx.globals().set(
|
||||||
"__verge_log__",
|
"__verge_log__",
|
||||||
Func::from(move |level: String, data: String| {
|
Func::from(move |level: String, data: String| {
|
||||||
let mut out = copy_outputs.lock().unwrap();
|
let mut out = copy_outputs.lock().unwrap();
|
||||||
out.push((level, data));
|
out.push((level, data));
|
||||||
}),
|
}),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
ctx.eval(
|
ctx.eval(
|
||||||
r#"var console = Object.freeze({
|
r#"var console = Object.freeze({
|
||||||
log(data){__verge_log__("log",JSON.stringify(data))},
|
log(data){__verge_log__("log",JSON.stringify(data))},
|
||||||
info(data){__verge_log__("info",JSON.stringify(data))},
|
info(data){__verge_log__("info",JSON.stringify(data))},
|
||||||
error(data){__verge_log__("error",JSON.stringify(data))},
|
error(data){__verge_log__("error",JSON.stringify(data))},
|
||||||
debug(data){__verge_log__("debug",JSON.stringify(data))},
|
debug(data){__verge_log__("debug",JSON.stringify(data))},
|
||||||
});"#,
|
});"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let config = use_lowercase(config.clone());
|
let config = use_lowercase(config.clone());
|
||||||
let config_str = serde_json::to_string(&config)?;
|
let config_str = serde_json::to_string(&config)?;
|
||||||
|
|
||||||
let code = format!(
|
let code = format!(
|
||||||
r#"try{{
|
r#"try{{
|
||||||
{script};
|
{script};
|
||||||
JSON.stringify(main({config_str})||'')
|
JSON.stringify(main({config_str})||'')
|
||||||
}} catch(err) {{
|
}} catch(err) {{
|
||||||
`__error_flag__ ${{err.toString()}}`
|
`__error_flag__ ${{err.toString()}}`
|
||||||
}}"#
|
}}"#
|
||||||
);
|
);
|
||||||
let result: String = ctx.eval(code.as_str())?;
|
let result: String = ctx.eval(code.as_str())?;
|
||||||
if result.starts_with("__error_flag__") {
|
if result.starts_with("__error_flag__") {
|
||||||
anyhow::bail!(result[15..].to_owned());
|
anyhow::bail!(result[15..].to_owned());
|
||||||
}
|
}
|
||||||
if result == "\"\"" {
|
if result == "\"\"" {
|
||||||
anyhow::bail!("main function should return object");
|
anyhow::bail!("main function should return object");
|
||||||
}
|
}
|
||||||
return Ok(serde_json::from_str::<Mapping>(result.as_str())?);
|
return Ok(serde_json::from_str::<Mapping>(result.as_str())?);
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut out = outputs.lock().unwrap();
|
let mut out = outputs.lock().unwrap();
|
||||||
match result {
|
match result {
|
||||||
Ok(config) => Ok((use_lowercase(config), out.to_vec())),
|
Ok(config) => Ok((use_lowercase(config), out.to_vec())),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
out.push(("exception".into(), err.to_string()));
|
out.push(("exception".into(), err.to_string()));
|
||||||
Ok((config, out.to_vec()))
|
Ok((config, out.to_vec()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_script() {
|
fn test_script() {
|
||||||
let script = r#"
|
let script = r#"
|
||||||
function main(config) {
|
function main(config) {
|
||||||
if (Array.isArray(config.rules)) {
|
if (Array.isArray(config.rules)) {
|
||||||
config.rules = [...config.rules, "add"];
|
config.rules = [...config.rules, "add"];
|
||||||
|
@ -73,7 +73,7 @@ fn test_script() {
|
||||||
}
|
}
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
let config = r#"
|
let config = r#"
|
||||||
rules:
|
rules:
|
||||||
- 111
|
- 111
|
||||||
- 222
|
- 222
|
||||||
|
@ -83,12 +83,12 @@ fn test_script() {
|
||||||
enable: false
|
enable: false
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
let config = serde_yaml::from_str(config).unwrap();
|
let config = serde_yaml::from_str(config).unwrap();
|
||||||
let (config, results) = use_script(script.into(), config).unwrap();
|
let (config, results) = use_script(script.into(), config).unwrap();
|
||||||
|
|
||||||
let config_str = serde_yaml::to_string(&config).unwrap();
|
let config_str = serde_yaml::to_string(&config).unwrap();
|
||||||
|
|
||||||
println!("{config_str}");
|
println!("{config_str}");
|
||||||
|
|
||||||
dbg!(results);
|
dbg!(results);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,81 +1,81 @@
|
||||||
use serde_yaml::{Mapping, Value};
|
use serde_yaml::{Mapping, Value};
|
||||||
|
|
||||||
macro_rules! revise {
|
macro_rules! revise {
|
||||||
($map: expr, $key: expr, $val: expr) => {
|
($map: expr, $key: expr, $val: expr) => {
|
||||||
let ret_key = Value::String($key.into());
|
let ret_key = Value::String($key.into());
|
||||||
$map.insert(ret_key, Value::from($val));
|
$map.insert(ret_key, Value::from($val));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// if key not exists then append value
|
// if key not exists then append value
|
||||||
macro_rules! append {
|
macro_rules! append {
|
||||||
($map: expr, $key: expr, $val: expr) => {
|
($map: expr, $key: expr, $val: expr) => {
|
||||||
let ret_key = Value::String($key.into());
|
let ret_key = Value::String($key.into());
|
||||||
if !$map.contains_key(&ret_key) {
|
if !$map.contains_key(&ret_key) {
|
||||||
$map.insert(ret_key, Value::from($val));
|
$map.insert(ret_key, Value::from($val));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
|
pub fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
|
||||||
let tun_key = Value::from("tun");
|
let tun_key = Value::from("tun");
|
||||||
let tun_val = config.get(&tun_key);
|
let tun_val = config.get(&tun_key);
|
||||||
|
|
||||||
if !enable && tun_val.is_none() {
|
if !enable && tun_val.is_none() {
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut tun_val = tun_val.map_or(Mapping::new(), |val| {
|
let mut tun_val = tun_val.map_or(Mapping::new(), |val| {
|
||||||
val.as_mapping().cloned().unwrap_or(Mapping::new())
|
val.as_mapping().cloned().unwrap_or(Mapping::new())
|
||||||
});
|
});
|
||||||
|
|
||||||
revise!(tun_val, "enable", enable);
|
revise!(tun_val, "enable", enable);
|
||||||
if enable {
|
if enable {
|
||||||
append!(tun_val, "stack", "gvisor");
|
append!(tun_val, "stack", "gvisor");
|
||||||
append!(tun_val, "dns-hijack", vec!["198.18.0.2:53"]);
|
append!(tun_val, "dns-hijack", vec!["198.18.0.2:53"]);
|
||||||
append!(tun_val, "auto-route", true);
|
append!(tun_val, "auto-route", true);
|
||||||
append!(tun_val, "auto-detect-interface", true);
|
append!(tun_val, "auto-detect-interface", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
revise!(config, "tun", tun_val);
|
revise!(config, "tun", tun_val);
|
||||||
|
|
||||||
if enable {
|
if enable {
|
||||||
use_dns_for_tun(config)
|
use_dns_for_tun(config)
|
||||||
} else {
|
} else {
|
||||||
config
|
config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn use_dns_for_tun(mut config: Mapping) -> Mapping {
|
fn use_dns_for_tun(mut config: Mapping) -> Mapping {
|
||||||
let dns_key = Value::from("dns");
|
let dns_key = Value::from("dns");
|
||||||
let dns_val = config.get(&dns_key);
|
let dns_val = config.get(&dns_key);
|
||||||
|
|
||||||
let mut dns_val = dns_val.map_or(Mapping::new(), |val| {
|
let mut dns_val = dns_val.map_or(Mapping::new(), |val| {
|
||||||
val.as_mapping().cloned().unwrap_or(Mapping::new())
|
val.as_mapping().cloned().unwrap_or(Mapping::new())
|
||||||
});
|
});
|
||||||
|
|
||||||
// 开启tun将同时开启dns
|
// 开启tun将同时开启dns
|
||||||
revise!(dns_val, "enable", true);
|
revise!(dns_val, "enable", true);
|
||||||
|
|
||||||
append!(dns_val, "enhanced-mode", "fake-ip");
|
append!(dns_val, "enhanced-mode", "fake-ip");
|
||||||
append!(dns_val, "fake-ip-range", "198.18.0.1/16");
|
append!(dns_val, "fake-ip-range", "198.18.0.1/16");
|
||||||
append!(
|
append!(
|
||||||
dns_val,
|
dns_val,
|
||||||
"nameserver",
|
"nameserver",
|
||||||
vec!["114.114.114.114", "223.5.5.5", "8.8.8.8"]
|
vec!["114.114.114.114", "223.5.5.5", "8.8.8.8"]
|
||||||
);
|
);
|
||||||
append!(dns_val, "fallback", vec![] as Vec<&str>);
|
append!(dns_val, "fallback", vec![] as Vec<&str>);
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
append!(
|
append!(
|
||||||
dns_val,
|
dns_val,
|
||||||
"fake-ip-filter",
|
"fake-ip-filter",
|
||||||
vec![
|
vec![
|
||||||
"dns.msftncsi.com",
|
"dns.msftncsi.com",
|
||||||
"www.msftncsi.com",
|
"www.msftncsi.com",
|
||||||
"www.msftconnecttest.com"
|
"www.msftconnecttest.com"
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
revise!(config, "dns", dns_val);
|
revise!(config, "dns", dns_val);
|
||||||
config
|
config
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,62 +5,61 @@ use tauri::{AppHandle, Manager, Window};
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct Handle {
|
pub struct Handle {
|
||||||
pub app_handle: Option<AppHandle>,
|
pub app_handle: Option<AppHandle>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handle {
|
impl Handle {
|
||||||
pub fn set_inner(&mut self, app_handle: AppHandle) {
|
pub fn set_inner(&mut self, app_handle: AppHandle) {
|
||||||
self.app_handle = Some(app_handle);
|
self.app_handle = Some(app_handle);
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_window(&self) -> Option<Window> {
|
|
||||||
self
|
|
||||||
.app_handle
|
|
||||||
.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_verge(&self) {
|
pub fn get_window(&self) -> Option<Window> {
|
||||||
if let Some(window) = self.get_window() {
|
self.app_handle
|
||||||
log_if_err!(window.emit("verge://refresh-verge-config", "yes"));
|
.as_ref()
|
||||||
|
.map_or(None, |a| a.get_window("main"))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused)]
|
pub fn refresh_clash(&self) {
|
||||||
pub fn refresh_profiles(&self) {
|
if let Some(window) = self.get_window() {
|
||||||
if let Some(window) = self.get_window() {
|
log_if_err!(window.emit("verge://refresh-clash-config", "yes"));
|
||||||
log_if_err!(window.emit("verge://refresh-profiles-config", "yes"));
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn notice_message(&self, status: String, msg: String) {
|
pub fn refresh_verge(&self) {
|
||||||
if let Some(window) = self.get_window() {
|
if let Some(window) = self.get_window() {
|
||||||
log_if_err!(window.emit("verge://notice-message", (status, msg)));
|
log_if_err!(window.emit("verge://refresh-verge-config", "yes"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_systray(&self) -> Result<()> {
|
#[allow(unused)]
|
||||||
if self.app_handle.is_none() {
|
pub fn refresh_profiles(&self) {
|
||||||
bail!("update_systray unhandle error");
|
if let Some(window) = self.get_window() {
|
||||||
|
log_if_err!(window.emit("verge://refresh-profiles-config", "yes"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let app_handle = self.app_handle.as_ref().unwrap();
|
|
||||||
Tray::update_systray(app_handle)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// update the system tray state
|
pub fn notice_message(&self, status: String, msg: String) {
|
||||||
pub fn update_systray_part(&self) -> Result<()> {
|
if let Some(window) = self.get_window() {
|
||||||
if self.app_handle.is_none() {
|
log_if_err!(window.emit("verge://notice-message", (status, msg)));
|
||||||
bail!("update_systray unhandle error");
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_systray(&self) -> Result<()> {
|
||||||
|
if self.app_handle.is_none() {
|
||||||
|
bail!("update_systray unhandle error");
|
||||||
|
}
|
||||||
|
let app_handle = self.app_handle.as_ref().unwrap();
|
||||||
|
Tray::update_systray(app_handle)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// update the system tray state
|
||||||
|
pub fn update_systray_part(&self) -> Result<()> {
|
||||||
|
if self.app_handle.is_none() {
|
||||||
|
bail!("update_systray unhandle error");
|
||||||
|
}
|
||||||
|
let app_handle = self.app_handle.as_ref().unwrap();
|
||||||
|
Tray::update_part(app_handle)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
let app_handle = self.app_handle.as_ref().unwrap();
|
|
||||||
Tray::update_part(app_handle)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,150 +4,150 @@ use std::collections::HashMap;
|
||||||
use tauri::{AppHandle, GlobalShortcutManager};
|
use tauri::{AppHandle, GlobalShortcutManager};
|
||||||
|
|
||||||
pub struct Hotkey {
|
pub struct Hotkey {
|
||||||
current: Vec<String>, // 保存当前的热键设置
|
current: Vec<String>, // 保存当前的热键设置
|
||||||
manager: Option<AppHandle>,
|
manager: Option<AppHandle>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hotkey {
|
impl Hotkey {
|
||||||
pub fn new() -> Hotkey {
|
pub fn new() -> Hotkey {
|
||||||
Hotkey {
|
Hotkey {
|
||||||
current: Vec::new(),
|
current: Vec::new(),
|
||||||
manager: None,
|
manager: None,
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(&mut self, app_handle: AppHandle) -> Result<()> {
|
|
||||||
self.manager = Some(app_handle);
|
|
||||||
let data = Data::global();
|
|
||||||
let verge = data.verge.lock();
|
|
||||||
|
|
||||||
if let Some(hotkeys) = verge.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()));
|
|
||||||
} else {
|
|
||||||
log::error!(target: "app", "invalid hotkey \"{}\":\"{}\"", key.unwrap_or("None"), func.unwrap_or("None"));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
self.current = hotkeys.clone();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
pub fn init(&mut self, app_handle: AppHandle) -> Result<()> {
|
||||||
}
|
self.manager = Some(app_handle);
|
||||||
|
let data = Data::global();
|
||||||
|
let verge = data.verge.lock();
|
||||||
|
|
||||||
fn get_manager(&self) -> Result<impl GlobalShortcutManager> {
|
if let Some(hotkeys) = verge.hotkeys.as_ref() {
|
||||||
if self.manager.is_none() {
|
for hotkey in hotkeys.iter() {
|
||||||
bail!("failed to get hotkey manager");
|
let mut iter = hotkey.split(',');
|
||||||
}
|
let func = iter.next();
|
||||||
Ok(self.manager.as_ref().unwrap().global_shortcut_manager())
|
let key = iter.next();
|
||||||
}
|
|
||||||
|
|
||||||
fn register(&mut self, hotkey: &str, func: &str) -> Result<()> {
|
if func.is_some() && key.is_some() {
|
||||||
let mut manager = self.get_manager()?;
|
log_if_err!(self.register(key.unwrap(), func.unwrap()));
|
||||||
|
} else {
|
||||||
if manager.is_registered(hotkey)? {
|
log::error!(target: "app", "invalid hotkey \"{}\":\"{}\"", key.unwrap_or("None"), func.unwrap_or("None"));
|
||||||
manager.unregister(hotkey)?;
|
}
|
||||||
}
|
}
|
||||||
|
self.current = hotkeys.clone();
|
||||||
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"),
|
|
||||||
"toggle_system_proxy" => || feat::toggle_system_proxy(),
|
|
||||||
"enable_system_proxy" => || feat::enable_system_proxy(),
|
|
||||||
"disable_system_proxy" => || feat::disable_system_proxy(),
|
|
||||||
"toggle_tun_mode" => || feat::toggle_tun_mode(),
|
|
||||||
"enable_tun_mode" => || feat::enable_tun_mode(),
|
|
||||||
"disable_tun_mode" => || feat::disable_tun_mode(),
|
|
||||||
|
|
||||||
_ => bail!("invalid function \"{func}\""),
|
|
||||||
};
|
|
||||||
|
|
||||||
manager.register(hotkey, f)?;
|
|
||||||
log::info!(target: "app", "register hotkey {hotkey} {func}");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unregister(&mut 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();
|
|
||||||
let old_map = Self::get_map_from_vec(¤t);
|
|
||||||
let new_map = Self::get_map_from_vec(&new_hotkeys);
|
|
||||||
|
|
||||||
let (del, add) = Self::get_diff(old_map, new_map);
|
|
||||||
|
|
||||||
del.iter().for_each(|key| {
|
|
||||||
let _ = self.unregister(key);
|
|
||||||
});
|
|
||||||
|
|
||||||
add.iter().for_each(|(key, func)| {
|
|
||||||
log_if_err!(self.register(key, func));
|
|
||||||
});
|
|
||||||
|
|
||||||
self.current = new_hotkeys;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_map_from_vec<'a>(hotkeys: &'a Vec<String>) -> HashMap<&'a str, &'a str> {
|
|
||||||
let mut map = HashMap::new();
|
|
||||||
|
|
||||||
hotkeys.iter().for_each(|hotkey| {
|
|
||||||
let mut iter = hotkey.split(',');
|
|
||||||
let func = iter.next();
|
|
||||||
let key = iter.next();
|
|
||||||
|
|
||||||
if func.is_some() && key.is_some() {
|
|
||||||
let func = func.unwrap().trim();
|
|
||||||
let key = key.unwrap().trim();
|
|
||||||
map.insert(key, func);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
map
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_diff<'a>(
|
|
||||||
old_map: HashMap<&'a str, &'a str>,
|
|
||||||
new_map: HashMap<&'a str, &'a str>,
|
|
||||||
) -> (Vec<&'a str>, Vec<(&'a str, &'a str)>) {
|
|
||||||
let mut del_list = vec![];
|
|
||||||
let mut add_list = vec![];
|
|
||||||
|
|
||||||
old_map.iter().for_each(|(&key, func)| {
|
|
||||||
match new_map.get(key) {
|
|
||||||
Some(new_func) => {
|
|
||||||
if new_func != func {
|
|
||||||
del_list.push(key);
|
|
||||||
add_list.push((key, *new_func));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None => del_list.push(key),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
new_map.iter().for_each(|(&key, &func)| {
|
Ok(())
|
||||||
if old_map.get(key).is_none() {
|
}
|
||||||
add_list.push((key, func));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
(del_list, add_list)
|
fn get_manager(&self) -> Result<impl GlobalShortcutManager> {
|
||||||
}
|
if self.manager.is_none() {
|
||||||
|
bail!("failed to get hotkey manager");
|
||||||
|
}
|
||||||
|
Ok(self.manager.as_ref().unwrap().global_shortcut_manager())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register(&mut self, hotkey: &str, func: &str) -> Result<()> {
|
||||||
|
let mut manager = self.get_manager()?;
|
||||||
|
|
||||||
|
if manager.is_registered(hotkey)? {
|
||||||
|
manager.unregister(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"),
|
||||||
|
"toggle_system_proxy" => || feat::toggle_system_proxy(),
|
||||||
|
"enable_system_proxy" => || feat::enable_system_proxy(),
|
||||||
|
"disable_system_proxy" => || feat::disable_system_proxy(),
|
||||||
|
"toggle_tun_mode" => || feat::toggle_tun_mode(),
|
||||||
|
"enable_tun_mode" => || feat::enable_tun_mode(),
|
||||||
|
"disable_tun_mode" => || feat::disable_tun_mode(),
|
||||||
|
|
||||||
|
_ => bail!("invalid function \"{func}\""),
|
||||||
|
};
|
||||||
|
|
||||||
|
manager.register(hotkey, f)?;
|
||||||
|
log::info!(target: "app", "register hotkey {hotkey} {func}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unregister(&mut 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();
|
||||||
|
let old_map = Self::get_map_from_vec(¤t);
|
||||||
|
let new_map = Self::get_map_from_vec(&new_hotkeys);
|
||||||
|
|
||||||
|
let (del, add) = Self::get_diff(old_map, new_map);
|
||||||
|
|
||||||
|
del.iter().for_each(|key| {
|
||||||
|
let _ = self.unregister(key);
|
||||||
|
});
|
||||||
|
|
||||||
|
add.iter().for_each(|(key, func)| {
|
||||||
|
log_if_err!(self.register(key, func));
|
||||||
|
});
|
||||||
|
|
||||||
|
self.current = new_hotkeys;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_map_from_vec<'a>(hotkeys: &'a Vec<String>) -> HashMap<&'a str, &'a str> {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
|
||||||
|
hotkeys.iter().for_each(|hotkey| {
|
||||||
|
let mut iter = hotkey.split(',');
|
||||||
|
let func = iter.next();
|
||||||
|
let key = iter.next();
|
||||||
|
|
||||||
|
if func.is_some() && key.is_some() {
|
||||||
|
let func = func.unwrap().trim();
|
||||||
|
let key = key.unwrap().trim();
|
||||||
|
map.insert(key, func);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_diff<'a>(
|
||||||
|
old_map: HashMap<&'a str, &'a str>,
|
||||||
|
new_map: HashMap<&'a str, &'a str>,
|
||||||
|
) -> (Vec<&'a str>, Vec<(&'a str, &'a str)>) {
|
||||||
|
let mut del_list = vec![];
|
||||||
|
let mut add_list = vec![];
|
||||||
|
|
||||||
|
old_map.iter().for_each(|(&key, func)| {
|
||||||
|
match new_map.get(key) {
|
||||||
|
Some(new_func) => {
|
||||||
|
if new_func != func {
|
||||||
|
del_list.push(key);
|
||||||
|
add_list.push((key, *new_func));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => del_list.push(key),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
new_map.iter().for_each(|(&key, &func)| {
|
||||||
|
if old_map.get(key).is_none() {
|
||||||
|
add_list.push((key, func));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
(del_list, add_list)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Hotkey {
|
impl Drop for Hotkey {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if let Ok(mut manager) = self.get_manager() {
|
if let Ok(mut manager) = self.get_manager() {
|
||||||
let _ = manager.unregister_all();
|
let _ = manager.unregister_all();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,328 +22,328 @@ pub use self::service::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Core {
|
pub struct Core {
|
||||||
pub service: Arc<Mutex<Service>>,
|
pub service: Arc<Mutex<Service>>,
|
||||||
pub sysopt: Arc<Mutex<Sysopt>>,
|
pub sysopt: Arc<Mutex<Sysopt>>,
|
||||||
pub timer: Arc<Mutex<Timer>>,
|
pub timer: Arc<Mutex<Timer>>,
|
||||||
pub hotkey: Arc<Mutex<Hotkey>>,
|
pub hotkey: Arc<Mutex<Hotkey>>,
|
||||||
pub runtime: Arc<Mutex<RuntimeResult>>,
|
pub runtime: Arc<Mutex<RuntimeResult>>,
|
||||||
pub handle: Arc<Mutex<Handle>>,
|
pub handle: Arc<Mutex<Handle>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Core {
|
impl Core {
|
||||||
pub fn global() -> &'static Core {
|
pub fn global() -> &'static Core {
|
||||||
static CORE: OnceCell<Core> = OnceCell::new();
|
static CORE: OnceCell<Core> = OnceCell::new();
|
||||||
|
|
||||||
CORE.get_or_init(|| Core {
|
CORE.get_or_init(|| Core {
|
||||||
service: Arc::new(Mutex::new(Service::new())),
|
service: Arc::new(Mutex::new(Service::new())),
|
||||||
sysopt: Arc::new(Mutex::new(Sysopt::new())),
|
sysopt: Arc::new(Mutex::new(Sysopt::new())),
|
||||||
timer: Arc::new(Mutex::new(Timer::new())),
|
timer: Arc::new(Mutex::new(Timer::new())),
|
||||||
hotkey: Arc::new(Mutex::new(Hotkey::new())),
|
hotkey: Arc::new(Mutex::new(Hotkey::new())),
|
||||||
runtime: Arc::new(Mutex::new(RuntimeResult::default())),
|
runtime: Arc::new(Mutex::new(RuntimeResult::default())),
|
||||||
handle: Arc::new(Mutex::new(Handle::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();
|
/// initialize the core state
|
||||||
let mut verge = global.verge.lock();
|
pub fn init(&self, app_handle: tauri::AppHandle) {
|
||||||
verge.patch_config(Verge {
|
// kill old clash process
|
||||||
clash_core: Some(clash_core.clone()),
|
Service::kill_old_clash();
|
||||||
..Verge::default()
|
|
||||||
})?;
|
|
||||||
drop(verge);
|
|
||||||
|
|
||||||
let mut service = self.service.lock();
|
let mut handle = self.handle.lock();
|
||||||
service.clear_logs();
|
handle.set_inner(app_handle.clone());
|
||||||
service.restart()?;
|
drop(handle);
|
||||||
drop(service);
|
|
||||||
|
|
||||||
self.activate()
|
let mut service = self.service.lock();
|
||||||
}
|
log_if_err!(service.start());
|
||||||
|
drop(service);
|
||||||
|
|
||||||
/// Patch Clash
|
log_if_err!(self.activate());
|
||||||
/// 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 mut sysopt = self.sysopt.lock();
|
||||||
let global = Data::global();
|
log_if_err!(sysopt.init_launch());
|
||||||
let mut clash = global.clash.lock();
|
log_if_err!(sysopt.init_sysproxy());
|
||||||
clash.patch_config(patch_cloned)?;
|
drop(sysopt);
|
||||||
clash.info.port.is_some()
|
|
||||||
};
|
|
||||||
|
|
||||||
// todo: port check
|
let handle = self.handle.lock();
|
||||||
if (mixed_port.is_some() && valid_port) || external.is_some() || secret.is_some() {
|
log_if_err!(handle.update_systray_part());
|
||||||
let mut service = self.service.lock();
|
drop(handle);
|
||||||
service.restart()?;
|
|
||||||
drop(service);
|
|
||||||
|
|
||||||
self.activate()?;
|
let mut hotkey = self.hotkey.lock();
|
||||||
|
log_if_err!(hotkey.init(app_handle));
|
||||||
|
drop(hotkey);
|
||||||
|
|
||||||
let mut sysopt = self.sysopt.lock();
|
// timer initialize
|
||||||
sysopt.init_sysproxy()?;
|
let mut timer = self.timer.lock();
|
||||||
|
log_if_err!(timer.restore());
|
||||||
}
|
}
|
||||||
|
|
||||||
if clash_mode.is_some() {
|
/// restart the clash sidecar
|
||||||
let handle = self.handle.lock();
|
pub fn restart_clash(&self) -> Result<()> {
|
||||||
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();
|
let mut service = self.service.lock();
|
||||||
service.restart()?;
|
service.restart()?;
|
||||||
drop(service);
|
drop(service);
|
||||||
}
|
self.activate()
|
||||||
|
}
|
||||||
|
|
||||||
if tun_mode.is_some() && *tun_mode.as_ref().unwrap_or(&false) {
|
/// change the clash core
|
||||||
let wintun_dll = crate::utils::dirs::app_home_dir().join("wintun.dll");
|
pub fn change_core(&self, clash_core: Option<String>) -> Result<()> {
|
||||||
if !wintun_dll.exists() {
|
let clash_core = clash_core.unwrap_or("clash".into());
|
||||||
bail!("failed to enable TUN for missing `wintun.dll`");
|
|
||||||
|
if &clash_core != "clash" && &clash_core != "clash-meta" {
|
||||||
|
bail!("invalid clash core name \"{clash_core}\"");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if service_mode.is_some() || tun_mode.is_some() {
|
let global = Data::global();
|
||||||
self.activate()?;
|
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
/// Patch Clash
|
||||||
if tun_mode.is_some() {
|
/// handle the clash config changed
|
||||||
self.activate()?;
|
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 mut sysopt = self.sysopt.lock();
|
let valid_port = {
|
||||||
|
let global = Data::global();
|
||||||
|
let mut clash = global.clash.lock();
|
||||||
|
clash.patch_config(patch_cloned)?;
|
||||||
|
clash.info.port.is_some()
|
||||||
|
};
|
||||||
|
|
||||||
if auto_launch.is_some() {
|
// todo: port check
|
||||||
sysopt.update_launch()?;
|
if (mixed_port.is_some() && valid_port) || external.is_some() || secret.is_some() {
|
||||||
}
|
let mut service = self.service.lock();
|
||||||
if system_proxy.is_some() || proxy_bypass.is_some() {
|
service.restart()?;
|
||||||
sysopt.update_sysproxy()?;
|
drop(service);
|
||||||
sysopt.guard_proxy();
|
|
||||||
}
|
|
||||||
if proxy_guard.unwrap_or(false) {
|
|
||||||
sysopt.guard_proxy();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新tray
|
self.activate()?;
|
||||||
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 sysopt = self.sysopt.lock();
|
||||||
let mut hotkey = self.hotkey.lock();
|
sysopt.init_sysproxy()?;
|
||||||
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();
|
if clash_mode.is_some() {
|
||||||
handle.notice_message("set_config::error".into(), format!("{err}"));
|
let handle = self.handle.lock();
|
||||||
log::error!(target: "app", "{err}")
|
handle.update_systray_part()?;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Static function
|
/// Patch Verge
|
||||||
/// update profile item
|
pub fn patch_verge(&self, patch: Verge) -> Result<()> {
|
||||||
pub async fn update_profile_item(&self, uid: String, option: Option<PrfOption>) -> Result<()> {
|
// save the patch
|
||||||
let global = Data::global();
|
let global = Data::global();
|
||||||
|
let mut verge = global.verge.lock();
|
||||||
|
verge.patch_config(patch.clone())?;
|
||||||
|
drop(verge);
|
||||||
|
|
||||||
let (url, opt) = {
|
let tun_mode = patch.enable_tun_mode;
|
||||||
let profiles = global.profiles.lock();
|
let auto_launch = patch.enable_auto_launch;
|
||||||
let item = profiles.get_item(&uid)?;
|
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;
|
||||||
|
|
||||||
if let Some(typ) = item.itype.as_ref() {
|
#[cfg(target_os = "windows")]
|
||||||
// maybe only valid for `local` profile
|
{
|
||||||
if *typ != "remote" {
|
let service_mode = patch.enable_service_mode;
|
||||||
// reactivate the config
|
|
||||||
if Some(uid) == profiles.get_current() {
|
// 重启服务
|
||||||
|
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);
|
drop(profiles);
|
||||||
self.activate()?;
|
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);
|
Ok(())
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,8 @@ use std::fs;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, VecDeque},
|
collections::{HashMap, VecDeque},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use tauri::api::process::{Command, CommandChild, CommandEvent};
|
use tauri::api::process::{Command, CommandChild, CommandEvent};
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
|
@ -19,209 +19,209 @@ const LOGS_QUEUE_LEN: usize = 100;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Service {
|
pub struct Service {
|
||||||
sidecar: Option<CommandChild>,
|
sidecar: Option<CommandChild>,
|
||||||
|
|
||||||
logs: Arc<RwLock<VecDeque<String>>>,
|
logs: Arc<RwLock<VecDeque<String>>>,
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
use_service_mode: bool,
|
use_service_mode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Service {
|
impl Service {
|
||||||
pub fn new() -> Service {
|
pub fn new() -> Service {
|
||||||
let queue = VecDeque::with_capacity(LOGS_QUEUE_LEN + 10);
|
let queue = VecDeque::with_capacity(LOGS_QUEUE_LEN + 10);
|
||||||
|
|
||||||
Service {
|
Service {
|
||||||
sidecar: None,
|
sidecar: None,
|
||||||
logs: Arc::new(RwLock::new(queue)),
|
logs: Arc::new(RwLock::new(queue)),
|
||||||
use_service_mode: false,
|
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 start(&mut self) -> Result<()> {
|
||||||
}
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
self.start_clash_by_sidecar()?;
|
||||||
|
|
||||||
pub fn stop(&mut self) -> Result<()> {
|
#[cfg(target_os = "windows")]
|
||||||
#[cfg(not(target_os = "windows"))]
|
{
|
||||||
self.stop_clash_by_sidecar()?;
|
let enable = {
|
||||||
|
let data = Data::global();
|
||||||
|
let verge = data.verge.lock();
|
||||||
|
verge.enable_service_mode.clone().unwrap_or(false)
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
self.use_service_mode = enable;
|
||||||
{
|
|
||||||
let _ = self.stop_clash_by_sidecar();
|
|
||||||
|
|
||||||
if self.use_service_mode {
|
if !enable {
|
||||||
tauri::async_runtime::block_on(async move {
|
return self.start_clash_by_sidecar();
|
||||||
log_if_err!(Self::stop_clash_by_service().await);
|
}
|
||||||
});
|
|
||||||
}
|
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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
pub fn stop(&mut self) -> Result<()> {
|
||||||
}
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
self.stop_clash_by_sidecar()?;
|
||||||
|
|
||||||
pub fn restart(&mut self) -> Result<()> {
|
#[cfg(target_os = "windows")]
|
||||||
self.stop()?;
|
{
|
||||||
self.start()
|
let _ = self.stop_clash_by_sidecar();
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_logs(&self) -> VecDeque<String> {
|
if self.use_service_mode {
|
||||||
self.logs.read().clone()
|
tauri::async_runtime::block_on(async move {
|
||||||
}
|
log_if_err!(Self::stop_clash_by_service().await);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
Ok(())
|
||||||
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 = {
|
pub fn restart(&mut self) -> Result<()> {
|
||||||
let global = Data::global();
|
self.stop()?;
|
||||||
let verge = global.verge.lock();
|
self.start()
|
||||||
verge.clash_core.clone().unwrap_or("clash".into())
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let app_dir = dirs::app_home_dir();
|
pub fn get_logs(&self) -> VecDeque<String> {
|
||||||
let app_dir = app_dir.as_os_str().to_str().unwrap();
|
self.logs.read().clone()
|
||||||
|
}
|
||||||
|
|
||||||
// fix #212
|
#[allow(unused)]
|
||||||
let args = match clash_core.as_str() {
|
pub fn set_logs(&self, text: String) {
|
||||||
"clash-meta" => vec!["-m", "-d", app_dir],
|
let mut logs = self.logs.write();
|
||||||
_ => vec!["-d", app_dir],
|
if logs.len() > LOGS_QUEUE_LEN {
|
||||||
};
|
(*logs).pop_front();
|
||||||
|
|
||||||
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);
|
(*logs).push_back(text);
|
||||||
};
|
}
|
||||||
|
|
||||||
while let Some(event) = rx.recv().await {
|
pub fn clear_logs(&self) {
|
||||||
match event {
|
let mut logs = self.logs.write();
|
||||||
CommandEvent::Stdout(line) => {
|
(*logs).clear();
|
||||||
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);
|
/// start the clash sidecar
|
||||||
write_log(line);
|
fn start_clash_by_sidecar(&mut self) -> Result<()> {
|
||||||
}
|
if self.sidecar.is_some() {
|
||||||
CommandEvent::Stderr(err) => {
|
let sidecar = self.sidecar.take().unwrap();
|
||||||
log::error!(target: "app" ,"[clash error]: {}", err);
|
let _ = sidecar.kill();
|
||||||
write_log(err);
|
|
||||||
}
|
|
||||||
CommandEvent::Error(err) => log::error!(target: "app" ,"{err}"),
|
|
||||||
CommandEvent::Terminated(_) => break,
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
let clash_core: String = {
|
||||||
}
|
let global = Data::global();
|
||||||
|
let verge = global.verge.lock();
|
||||||
|
verge.clash_core.clone().unwrap_or("clash".into())
|
||||||
|
};
|
||||||
|
|
||||||
/// stop the clash sidecar
|
let app_dir = dirs::app_home_dir();
|
||||||
fn stop_clash_by_sidecar(&mut self) -> Result<()> {
|
let app_dir = app_dir.as_os_str().to_str().unwrap();
|
||||||
if let Some(sidecar) = self.sidecar.take() {
|
|
||||||
sidecar.kill()?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_start(&mut self) -> Result<()> {
|
// fix #212
|
||||||
#[cfg(target_os = "windows")]
|
let args = match clash_core.as_str() {
|
||||||
{
|
"clash-meta" => vec!["-m", "-d", app_dir],
|
||||||
let global = Data::global();
|
_ => vec!["-d", app_dir],
|
||||||
let verge = global.verge.lock();
|
};
|
||||||
let service_mode = verge.enable_service_mode.unwrap_or(false);
|
|
||||||
|
|
||||||
if !service_mode && self.sidecar.is_none() {
|
let cmd = Command::new_sidecar(clash_core)?;
|
||||||
self.start()?;
|
|
||||||
}
|
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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
/// stop the clash sidecar
|
||||||
if self.sidecar.is_none() {
|
fn stop_clash_by_sidecar(&mut self) -> Result<()> {
|
||||||
self.start()?;
|
if let Some(sidecar) = self.sidecar.take() {
|
||||||
|
sidecar.kill()?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
/// update clash config
|
if !service_mode && self.sidecar.is_none() {
|
||||||
/// using PUT methods
|
self.start()?;
|
||||||
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)?;
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
if self.sidecar.is_none() {
|
||||||
|
self.start()?;
|
||||||
|
}
|
||||||
|
|
||||||
let mut data = HashMap::new();
|
Ok(())
|
||||||
data.insert("path", temp_path.as_os_str().to_str().unwrap());
|
}
|
||||||
|
|
||||||
macro_rules! report_err {
|
/// 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) => {
|
($i: expr, $e: expr) => {
|
||||||
match $i {
|
match $i {
|
||||||
4 => bail!($e),
|
4 => bail!($e),
|
||||||
|
@ -230,306 +230,308 @@ impl Service {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// retry 5 times
|
// retry 5 times
|
||||||
for i in 0..5 {
|
for i in 0..5 {
|
||||||
let headers = headers.clone();
|
let headers = headers.clone();
|
||||||
match reqwest::ClientBuilder::new().no_proxy().build() {
|
match reqwest::ClientBuilder::new().no_proxy().build() {
|
||||||
Ok(client) => {
|
Ok(client) => {
|
||||||
let builder = client.put(&server).headers(headers).json(&data);
|
let builder = client.put(&server).headers(headers).json(&data);
|
||||||
match builder.send().await {
|
match builder.send().await {
|
||||||
Ok(resp) => match resp.status().as_u16() {
|
Ok(resp) => match resp.status().as_u16() {
|
||||||
204 => break,
|
204 => break,
|
||||||
// 配置有问题不重试
|
// 配置有问题不重试
|
||||||
400 => bail!("failed to update clash config with status 400"),
|
400 => bail!("failed to update clash config with status 400"),
|
||||||
status @ _ => report_err!(i, "failed to activate clash with status \"{status}\""),
|
status @ _ => {
|
||||||
},
|
report_err!(i, "failed to activate clash with status \"{status}\"")
|
||||||
Err(err) => report_err!(i, "{err}"),
|
}
|
||||||
}
|
},
|
||||||
|
Err(err) => report_err!(i, "{err}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => report_err!(i, "{err}"),
|
||||||
|
}
|
||||||
|
sleep(Duration::from_millis(500)).await;
|
||||||
}
|
}
|
||||||
Err(err) => report_err!(i, "{err}"),
|
|
||||||
}
|
Ok(())
|
||||||
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)?;
|
||||||
|
|
||||||
/// patch clash config
|
let client = reqwest::ClientBuilder::new().no_proxy().build()?;
|
||||||
pub async fn patch_config(info: ClashInfo, config: Mapping) -> Result<()> {
|
let builder = client.patch(&server).headers(headers.clone()).json(&config);
|
||||||
let (server, headers) = Self::clash_client_info(info)?;
|
builder.send().await?;
|
||||||
|
Ok(())
|
||||||
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();
|
/// get clash client url and headers from clash info
|
||||||
let server = format!("http://{server}/configs");
|
fn clash_client_info(info: ClashInfo) -> Result<(String, HeaderMap)> {
|
||||||
|
if info.server.is_none() {
|
||||||
let mut headers = HeaderMap::new();
|
let status = &info.status;
|
||||||
headers.insert("Content-Type", "application/json".parse().unwrap());
|
if info.port.is_none() {
|
||||||
|
bail!("failed to parse config.yaml file with status {status}");
|
||||||
if let Some(secret) = info.secret.as_ref() {
|
} else {
|
||||||
let secret = format!("Bearer {}", secret.clone()).parse().unwrap();
|
bail!("failed to parse the server with status {status}");
|
||||||
headers.insert("Authorization", secret);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((server, headers))
|
let server = info.server.unwrap();
|
||||||
}
|
let server = format!("http://{server}/configs");
|
||||||
|
|
||||||
/// kill old clash process
|
let mut headers = HeaderMap::new();
|
||||||
pub fn kill_old_clash() {
|
headers.insert("Content-Type", "application/json".parse().unwrap());
|
||||||
use sysinfo::{Pid, PidExt, ProcessExt, System, SystemExt};
|
|
||||||
|
if let Some(secret) = info.secret.as_ref() {
|
||||||
if let Ok(pid) = fs::read(dirs::clash_pid_path()) {
|
let secret = format!("Bearer {}", secret.clone()).parse().unwrap();
|
||||||
if let Ok(pid) = String::from_utf8_lossy(&pid).parse() {
|
headers.insert("Authorization", secret);
|
||||||
let mut system = System::new();
|
}
|
||||||
system.refresh_all();
|
|
||||||
|
Ok((server, headers))
|
||||||
let proc = system.process(Pid::from_u32(pid));
|
}
|
||||||
if let Some(proc) = proc {
|
|
||||||
proc.kill();
|
/// 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 {
|
impl Drop for Service {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
log_if_err!(self.stop());
|
log_if_err!(self.stop());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ### Service Mode
|
/// ### Service Mode
|
||||||
///
|
///
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub mod win_service {
|
pub mod win_service {
|
||||||
use super::*;
|
use super::*;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use deelevate::{PrivilegeLevel, Token};
|
use deelevate::{PrivilegeLevel, Token};
|
||||||
use runas::Command as RunasCommand;
|
use runas::Command as RunasCommand;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::os::windows::process::CommandExt;
|
use std::os::windows::process::CommandExt;
|
||||||
use std::{env::current_exe, process::Command as StdCommand};
|
use std::{env::current_exe, process::Command as StdCommand};
|
||||||
|
|
||||||
const SERVICE_NAME: &str = "clash_verge_service";
|
const SERVICE_NAME: &str = "clash_verge_service";
|
||||||
|
|
||||||
const SERVICE_URL: &str = "http://127.0.0.1:33211";
|
const SERVICE_URL: &str = "http://127.0.0.1:33211";
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
pub struct ResponseBody {
|
pub struct ResponseBody {
|
||||||
pub bin_path: String,
|
pub bin_path: String,
|
||||||
pub config_dir: String,
|
pub config_dir: String,
|
||||||
pub log_file: 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
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
/// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程
|
pub struct JsonResponse {
|
||||||
pub async fn uninstall_service() -> Result<()> {
|
pub code: u64,
|
||||||
let binary_path = dirs::service_path();
|
pub msg: String,
|
||||||
let uninstall_path = binary_path.with_file_name("uninstall-service.exe");
|
pub data: Option<ResponseBody>,
|
||||||
|
|
||||||
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]
|
impl Service {
|
||||||
/// start service
|
/// Install the Clash Verge Service
|
||||||
/// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程
|
/// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程
|
||||||
pub async fn start_service() -> Result<()> {
|
pub async fn install_service() -> Result<()> {
|
||||||
let token = Token::with_current_process()?;
|
let binary_path = dirs::service_path();
|
||||||
let level = token.privilege_level()?;
|
let install_path = binary_path.with_file_name("install-service.exe");
|
||||||
|
|
||||||
let args = ["start", SERVICE_NAME];
|
if !install_path.exists() {
|
||||||
|
bail!("installer exe not found");
|
||||||
|
}
|
||||||
|
|
||||||
let status = match level {
|
let token = Token::with_current_process()?;
|
||||||
PrivilegeLevel::NotPrivileged => RunasCommand::new("sc").args(&args).status()?,
|
let level = token.privilege_level()?;
|
||||||
_ => StdCommand::new("sc").args(&args).status()?,
|
|
||||||
};
|
|
||||||
|
|
||||||
match status.success() {
|
let status = match level {
|
||||||
true => Ok(()),
|
PrivilegeLevel::NotPrivileged => RunasCommand::new(install_path).status()?,
|
||||||
false => bail!(
|
_ => StdCommand::new(install_path)
|
||||||
"failed to start service with status {}",
|
.creation_flags(0x08000000)
|
||||||
status.code().unwrap()
|
.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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,18 +6,18 @@ use sysproxy::Sysproxy;
|
||||||
use tauri::{async_runtime::Mutex, utils::platform::current_exe};
|
use tauri::{async_runtime::Mutex, utils::platform::current_exe};
|
||||||
|
|
||||||
pub struct Sysopt {
|
pub struct Sysopt {
|
||||||
/// current system proxy setting
|
/// current system proxy setting
|
||||||
cur_sysproxy: Option<Sysproxy>,
|
cur_sysproxy: Option<Sysproxy>,
|
||||||
|
|
||||||
/// record the original system proxy
|
/// record the original system proxy
|
||||||
/// recover it when exit
|
/// recover it when exit
|
||||||
old_sysproxy: Option<Sysproxy>,
|
old_sysproxy: Option<Sysproxy>,
|
||||||
|
|
||||||
/// helps to auto launch the app
|
/// helps to auto launch the app
|
||||||
auto_launch: Option<AutoLaunch>,
|
auto_launch: Option<AutoLaunch>,
|
||||||
|
|
||||||
/// record whether the guard async is running or not
|
/// record whether the guard async is running or not
|
||||||
guard_state: Arc<Mutex<bool>>,
|
guard_state: Arc<Mutex<bool>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
|
@ -28,258 +28,258 @@ static DEFAULT_BYPASS: &str = "localhost,127.0.0.1/8,::1";
|
||||||
static DEFAULT_BYPASS: &str = "127.0.0.1,localhost,<local>";
|
static DEFAULT_BYPASS: &str = "127.0.0.1,localhost,<local>";
|
||||||
|
|
||||||
impl Sysopt {
|
impl Sysopt {
|
||||||
pub fn new() -> Sysopt {
|
pub fn new() -> Sysopt {
|
||||||
Sysopt {
|
Sysopt {
|
||||||
cur_sysproxy: None,
|
cur_sysproxy: None,
|
||||||
old_sysproxy: None,
|
old_sysproxy: None,
|
||||||
auto_launch: None,
|
auto_launch: None,
|
||||||
guard_state: Arc::new(Mutex::new(false)),
|
guard_state: Arc::new(Mutex::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();
|
|
||||||
|
|
||||||
if port.is_none() {
|
|
||||||
bail!("clash port is none");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let verge = data.verge.lock();
|
/// 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();
|
||||||
|
|
||||||
let enable = verge.enable_system_proxy.clone().unwrap_or(false);
|
if port.is_none() {
|
||||||
let bypass = verge.system_proxy_bypass.clone();
|
bail!("clash port is none");
|
||||||
let bypass = bypass.unwrap_or(DEFAULT_BYPASS.into());
|
}
|
||||||
|
|
||||||
let port = port.unwrap().parse::<u16>()?;
|
let verge = data.verge.lock();
|
||||||
let host = String::from("127.0.0.1");
|
|
||||||
|
|
||||||
self.cur_sysproxy = Some(Sysproxy {
|
|
||||||
enable,
|
|
||||||
host,
|
|
||||||
port,
|
|
||||||
bypass,
|
|
||||||
});
|
|
||||||
|
|
||||||
if enable {
|
|
||||||
self.old_sysproxy = Sysproxy::get_system_proxy().map_or(None, |p| Some(p));
|
|
||||||
self.cur_sysproxy.as_ref().unwrap().set_system_proxy()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// run the system proxy guard
|
|
||||||
self.guard_proxy();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// update the system proxy
|
|
||||||
pub fn update_sysproxy(&mut self) -> Result<()> {
|
|
||||||
if self.cur_sysproxy.is_none() || self.old_sysproxy.is_none() {
|
|
||||||
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();
|
|
||||||
|
|
||||||
sysproxy.enable = enable;
|
|
||||||
sysproxy.bypass = bypass;
|
|
||||||
|
|
||||||
self.cur_sysproxy = Some(sysproxy);
|
|
||||||
self.cur_sysproxy.as_ref().unwrap().set_system_proxy()?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// reset the sysproxy
|
|
||||||
pub fn reset_sysproxy(&mut self) -> Result<()> {
|
|
||||||
let cur = self.cur_sysproxy.take();
|
|
||||||
|
|
||||||
if let Some(mut old) = self.old_sysproxy.take() {
|
|
||||||
// 如果原代理和当前代理 端口一致,就disable关闭,否则就恢复原代理设置
|
|
||||||
// 当前没有设置代理的时候,不确定旧设置是否和当前一致,全关了
|
|
||||||
let port_same = cur.map_or(true, |cur| old.port == cur.port);
|
|
||||||
|
|
||||||
if old.enable && port_same {
|
|
||||||
old.enable = false;
|
|
||||||
log::info!(target: "app", "reset proxy by disabling the original proxy");
|
|
||||||
} else {
|
|
||||||
log::info!(target: "app", "reset proxy to the original proxy");
|
|
||||||
}
|
|
||||||
|
|
||||||
old.set_system_proxy()?;
|
|
||||||
} else if let Some(mut cur @ Sysproxy { enable: true, .. }) = cur {
|
|
||||||
// 没有原代理,就按现在的代理设置disable即可
|
|
||||||
log::info!(target: "app", "reset proxy by disabling the current proxy");
|
|
||||||
cur.enable = false;
|
|
||||||
cur.set_system_proxy()?;
|
|
||||||
} else {
|
|
||||||
log::info!(target: "app", "reset proxy with no action");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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);
|
|
||||||
|
|
||||||
let app_exe = current_exe()?;
|
|
||||||
let app_exe = dunce::canonicalize(app_exe)?;
|
|
||||||
let app_name = app_exe
|
|
||||||
.file_stem()
|
|
||||||
.and_then(|f| f.to_str())
|
|
||||||
.ok_or(anyhow!("failed to get file stem"))?;
|
|
||||||
|
|
||||||
let app_path = app_exe
|
|
||||||
.as_os_str()
|
|
||||||
.to_str()
|
|
||||||
.ok_or(anyhow!("failed to get app_path"))?
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
// fix issue #26
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
let app_path = format!("\"{app_path}\"");
|
|
||||||
|
|
||||||
// use the /Applications/Clash Verge.app path
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
let app_path = (|| -> Option<String> {
|
|
||||||
let path = std::path::PathBuf::from(&app_path);
|
|
||||||
let path = path.parent()?.parent()?.parent()?;
|
|
||||||
let extension = path.extension()?.to_str()?;
|
|
||||||
match extension == "app" {
|
|
||||||
true => Some(path.as_os_str().to_str()?.to_string()),
|
|
||||||
false => None,
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
.unwrap_or(app_path);
|
|
||||||
|
|
||||||
let auto = AutoLaunchBuilder::new()
|
|
||||||
.set_app_name(app_name)
|
|
||||||
.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()?,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// update the startup
|
|
||||||
pub fn update_launch(&mut self) -> Result<()> {
|
|
||||||
if self.auto_launch.is_none() {
|
|
||||||
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();
|
|
||||||
|
|
||||||
match enable {
|
|
||||||
true => auto_launch.enable()?,
|
|
||||||
false => crate::log_if_err!(auto_launch.disable()), // 忽略关闭的错误
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// launch a system proxy guard
|
|
||||||
/// read config from file directly
|
|
||||||
pub fn guard_proxy(&self) {
|
|
||||||
use tokio::time::{sleep, Duration};
|
|
||||||
|
|
||||||
let guard_state = self.guard_state.clone();
|
|
||||||
|
|
||||||
tauri::async_runtime::spawn(async move {
|
|
||||||
// if it is running, exit
|
|
||||||
let mut state = guard_state.lock().await;
|
|
||||||
if *state {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
*state = true;
|
|
||||||
drop(state);
|
|
||||||
|
|
||||||
// default duration is 10s
|
|
||||||
let mut wait_secs = 10u64;
|
|
||||||
|
|
||||||
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 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();
|
let bypass = verge.system_proxy_bypass.clone();
|
||||||
drop(verge);
|
let bypass = bypass.unwrap_or(DEFAULT_BYPASS.into());
|
||||||
|
|
||||||
// stop loop
|
let port = port.unwrap().parse::<u16>()?;
|
||||||
if !enable || !guard {
|
let host = String::from("127.0.0.1");
|
||||||
break;
|
|
||||||
|
self.cur_sysproxy = Some(Sysproxy {
|
||||||
|
enable,
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
bypass,
|
||||||
|
});
|
||||||
|
|
||||||
|
if enable {
|
||||||
|
self.old_sysproxy = Sysproxy::get_system_proxy().map_or(None, |p| Some(p));
|
||||||
|
self.cur_sysproxy.as_ref().unwrap().set_system_proxy()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update duration
|
// run the system proxy guard
|
||||||
wait_secs = guard_duration;
|
self.guard_proxy();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
let clash = global.clash.lock();
|
/// update the system proxy
|
||||||
let port = clash.info.port.clone();
|
pub fn update_sysproxy(&mut self) -> Result<()> {
|
||||||
let port = port.unwrap_or("".into()).parse::<u16>();
|
if self.cur_sysproxy.is_none() || self.old_sysproxy.is_none() {
|
||||||
drop(clash);
|
return self.init_sysproxy();
|
||||||
|
}
|
||||||
|
|
||||||
log::debug!(target: "app", "try to guard the system proxy");
|
let data = Data::global();
|
||||||
|
let verge = data.verge.lock();
|
||||||
|
|
||||||
match port {
|
let enable = verge.enable_system_proxy.clone().unwrap_or(false);
|
||||||
Ok(port) => {
|
let bypass = verge.system_proxy_bypass.clone();
|
||||||
let sysproxy = Sysproxy {
|
let bypass = bypass.unwrap_or(DEFAULT_BYPASS.into());
|
||||||
enable: true,
|
|
||||||
host: "127.0.0.1".into(),
|
let mut sysproxy = self.cur_sysproxy.take().unwrap();
|
||||||
port,
|
|
||||||
bypass: bypass.unwrap_or(DEFAULT_BYPASS.into()),
|
sysproxy.enable = enable;
|
||||||
|
sysproxy.bypass = bypass;
|
||||||
|
|
||||||
|
self.cur_sysproxy = Some(sysproxy);
|
||||||
|
self.cur_sysproxy.as_ref().unwrap().set_system_proxy()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// reset the sysproxy
|
||||||
|
pub fn reset_sysproxy(&mut self) -> Result<()> {
|
||||||
|
let cur = self.cur_sysproxy.take();
|
||||||
|
|
||||||
|
if let Some(mut old) = self.old_sysproxy.take() {
|
||||||
|
// 如果原代理和当前代理 端口一致,就disable关闭,否则就恢复原代理设置
|
||||||
|
// 当前没有设置代理的时候,不确定旧设置是否和当前一致,全关了
|
||||||
|
let port_same = cur.map_or(true, |cur| old.port == cur.port);
|
||||||
|
|
||||||
|
if old.enable && port_same {
|
||||||
|
old.enable = false;
|
||||||
|
log::info!(target: "app", "reset proxy by disabling the original proxy");
|
||||||
|
} else {
|
||||||
|
log::info!(target: "app", "reset proxy to the original proxy");
|
||||||
|
}
|
||||||
|
|
||||||
|
old.set_system_proxy()?;
|
||||||
|
} else if let Some(mut cur @ Sysproxy { enable: true, .. }) = cur {
|
||||||
|
// 没有原代理,就按现在的代理设置disable即可
|
||||||
|
log::info!(target: "app", "reset proxy by disabling the current proxy");
|
||||||
|
cur.enable = false;
|
||||||
|
cur.set_system_proxy()?;
|
||||||
|
} else {
|
||||||
|
log::info!(target: "app", "reset proxy with no action");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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);
|
||||||
|
|
||||||
|
let app_exe = current_exe()?;
|
||||||
|
let app_exe = dunce::canonicalize(app_exe)?;
|
||||||
|
let app_name = app_exe
|
||||||
|
.file_stem()
|
||||||
|
.and_then(|f| f.to_str())
|
||||||
|
.ok_or(anyhow!("failed to get file stem"))?;
|
||||||
|
|
||||||
|
let app_path = app_exe
|
||||||
|
.as_os_str()
|
||||||
|
.to_str()
|
||||||
|
.ok_or(anyhow!("failed to get app_path"))?
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
// fix issue #26
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
let app_path = format!("\"{app_path}\"");
|
||||||
|
|
||||||
|
// use the /Applications/Clash Verge.app path
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
let app_path = (|| -> Option<String> {
|
||||||
|
let path = std::path::PathBuf::from(&app_path);
|
||||||
|
let path = path.parent()?.parent()?.parent()?;
|
||||||
|
let extension = path.extension()?.to_str()?;
|
||||||
|
match extension == "app" {
|
||||||
|
true => Some(path.as_os_str().to_str()?.to_string()),
|
||||||
|
false => None,
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
.unwrap_or(app_path);
|
||||||
|
|
||||||
|
let auto = AutoLaunchBuilder::new()
|
||||||
|
.set_app_name(app_name)
|
||||||
|
.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()?,
|
||||||
};
|
};
|
||||||
|
|
||||||
log_if_err!(sysproxy.set_system_proxy());
|
|
||||||
}
|
|
||||||
Err(_) => log::error!(target: "app", "failed to parse clash port"),
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let mut state = guard_state.lock().await;
|
Ok(())
|
||||||
*state = false;
|
}
|
||||||
});
|
|
||||||
}
|
/// update the startup
|
||||||
|
pub fn update_launch(&mut self) -> Result<()> {
|
||||||
|
if self.auto_launch.is_none() {
|
||||||
|
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();
|
||||||
|
|
||||||
|
match enable {
|
||||||
|
true => auto_launch.enable()?,
|
||||||
|
false => crate::log_if_err!(auto_launch.disable()), // 忽略关闭的错误
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// launch a system proxy guard
|
||||||
|
/// read config from file directly
|
||||||
|
pub fn guard_proxy(&self) {
|
||||||
|
use tokio::time::{sleep, Duration};
|
||||||
|
|
||||||
|
let guard_state = self.guard_state.clone();
|
||||||
|
|
||||||
|
tauri::async_runtime::spawn(async move {
|
||||||
|
// if it is running, exit
|
||||||
|
let mut state = guard_state.lock().await;
|
||||||
|
if *state {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*state = true;
|
||||||
|
drop(state);
|
||||||
|
|
||||||
|
// default duration is 10s
|
||||||
|
let mut wait_secs = 10u64;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// stop loop
|
||||||
|
if !enable || !guard {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
Ok(port) => {
|
||||||
|
let sysproxy = Sysproxy {
|
||||||
|
enable: true,
|
||||||
|
host: "127.0.0.1".into(),
|
||||||
|
port,
|
||||||
|
bypass: bypass.unwrap_or(DEFAULT_BYPASS.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
log_if_err!(sysproxy.set_system_proxy());
|
||||||
|
}
|
||||||
|
Err(_) => log::error!(target: "app", "failed to parse clash port"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut state = guard_state.lock().await;
|
||||||
|
*state = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,165 +8,165 @@ use std::collections::HashMap;
|
||||||
type TaskID = u64;
|
type TaskID = u64;
|
||||||
|
|
||||||
pub struct Timer {
|
pub struct Timer {
|
||||||
/// cron manager
|
/// cron manager
|
||||||
delay_timer: DelayTimer,
|
delay_timer: DelayTimer,
|
||||||
|
|
||||||
/// save the current state
|
/// save the current state
|
||||||
timer_map: HashMap<String, (TaskID, u64)>,
|
timer_map: HashMap<String, (TaskID, u64)>,
|
||||||
|
|
||||||
/// increment id
|
/// increment id
|
||||||
timer_count: TaskID,
|
timer_count: TaskID,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Timer {
|
impl Timer {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Timer {
|
Timer {
|
||||||
delay_timer: DelayTimerBuilder::default().build(),
|
delay_timer: DelayTimerBuilder::default().build(),
|
||||||
timer_map: HashMap::new(),
|
timer_map: HashMap::new(),
|
||||||
timer_count: 1,
|
timer_count: 1,
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Correctly update all cron tasks
|
|
||||||
pub fn refresh(&mut self) -> Result<()> {
|
|
||||||
let diff_map = self.gen_diff();
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
/// Correctly update all cron tasks
|
||||||
}
|
pub fn refresh(&mut self) -> Result<()> {
|
||||||
|
let diff_map = self.gen_diff();
|
||||||
|
|
||||||
/// restore timer
|
for (uid, diff) in diff_map.into_iter() {
|
||||||
pub fn restore(&mut self) -> Result<()> {
|
match diff {
|
||||||
self.refresh()?;
|
DiffFlag::Del(tid) => {
|
||||||
|
let _ = self.timer_map.remove(&uid);
|
||||||
let cur_timestamp = get_now(); // seconds
|
log_if_err!(self.delay_timer.remove_task(tid));
|
||||||
|
}
|
||||||
let global = Data::global();
|
DiffFlag::Add(tid, val) => {
|
||||||
let profiles = global.profiles.lock();
|
let _ = self.timer_map.insert(uid.clone(), (tid, val));
|
||||||
|
log_if_err!(self.add_task(uid, tid, val));
|
||||||
profiles
|
}
|
||||||
.get_items()
|
DiffFlag::Mod(tid, val) => {
|
||||||
.unwrap_or(&vec![])
|
let _ = self.timer_map.insert(uid.clone(), (tid, val));
|
||||||
.iter()
|
log_if_err!(self.delay_timer.remove_task(tid));
|
||||||
.filter(|item| item.uid.is_some() && item.updated.is_some() && item.option.is_some())
|
log_if_err!(self.add_task(uid, tid, val));
|
||||||
.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));
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
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() {
|
|
||||||
for item in items.iter() {
|
|
||||||
if item.option.is_some() {
|
|
||||||
let option = item.option.as_ref().unwrap();
|
|
||||||
let interval = option.update_interval.unwrap_or(0);
|
|
||||||
|
|
||||||
if interval > 0 {
|
|
||||||
new_map.insert(item.uid.clone().unwrap(), interval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
new_map
|
/// restore timer
|
||||||
}
|
pub fn restore(&mut self) -> Result<()> {
|
||||||
|
self.refresh()?;
|
||||||
|
|
||||||
/// generate the diff map for refresh
|
let cur_timestamp = get_now(); // seconds
|
||||||
fn gen_diff(&mut self) -> HashMap<String, DiffFlag> {
|
|
||||||
let mut diff_map = HashMap::new();
|
|
||||||
|
|
||||||
let new_map = self.gen_map();
|
let global = Data::global();
|
||||||
let cur_map = &self.timer_map;
|
let profiles = global.profiles.lock();
|
||||||
|
|
||||||
cur_map.iter().for_each(|(uid, (tid, val))| {
|
profiles
|
||||||
let new_val = new_map.get(uid).unwrap_or(&0);
|
.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));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if *new_val == 0 {
|
Ok(())
|
||||||
diff_map.insert(uid.clone(), DiffFlag::Del(*tid));
|
}
|
||||||
} else if new_val != val {
|
|
||||||
diff_map.insert(uid.clone(), DiffFlag::Mod(*tid, *new_val));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut count = self.timer_count;
|
/// generate a uid -> update_interval map
|
||||||
|
fn gen_map(&self) -> HashMap<String, u64> {
|
||||||
|
let global = Data::global();
|
||||||
|
let profiles = global.profiles.lock();
|
||||||
|
|
||||||
new_map.iter().for_each(|(uid, val)| {
|
let mut new_map = HashMap::new();
|
||||||
if cur_map.get(uid).is_none() {
|
|
||||||
diff_map.insert(uid.clone(), DiffFlag::Add(count, *val));
|
|
||||||
|
|
||||||
count += 1;
|
if let Some(items) = profiles.get_items() {
|
||||||
}
|
for item in items.iter() {
|
||||||
});
|
if item.option.is_some() {
|
||||||
|
let option = item.option.as_ref().unwrap();
|
||||||
|
let interval = option.update_interval.unwrap_or(0);
|
||||||
|
|
||||||
self.timer_count = count;
|
if interval > 0 {
|
||||||
|
new_map.insert(item.uid.clone().unwrap(), interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
diff_map
|
new_map
|
||||||
}
|
}
|
||||||
|
|
||||||
/// add a cron task
|
/// generate the diff map for refresh
|
||||||
fn add_task(&self, uid: String, tid: TaskID, minutes: u64) -> Result<()> {
|
fn gen_diff(&mut self) -> HashMap<String, DiffFlag> {
|
||||||
let core = Core::global();
|
let mut diff_map = HashMap::new();
|
||||||
|
|
||||||
let task = TaskBuilder::default()
|
let new_map = self.gen_map();
|
||||||
.set_task_id(tid)
|
let cur_map = &self.timer_map;
|
||||||
.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()))
|
|
||||||
.context("failed to create timer task")?;
|
|
||||||
|
|
||||||
self
|
cur_map.iter().for_each(|(uid, (tid, val))| {
|
||||||
.delay_timer
|
let new_val = new_map.get(uid).unwrap_or(&0);
|
||||||
.add_task(task)
|
|
||||||
.context("failed to add timer task")?;
|
|
||||||
|
|
||||||
Ok(())
|
if *new_val == 0 {
|
||||||
}
|
diff_map.insert(uid.clone(), DiffFlag::Del(*tid));
|
||||||
|
} else if new_val != val {
|
||||||
|
diff_map.insert(uid.clone(), DiffFlag::Mod(*tid, *new_val));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/// the task runner
|
let mut count = self.timer_count;
|
||||||
async fn async_task(core: Core, uid: String) {
|
|
||||||
log::info!(target: "app", "running timer task `{uid}`");
|
new_map.iter().for_each(|(uid, val)| {
|
||||||
log_if_err!(core.update_profile_item(uid, None).await);
|
if cur_map.get(uid).is_none() {
|
||||||
}
|
diff_map.insert(uid.clone(), DiffFlag::Add(count, *val));
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
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()))
|
||||||
|
.context("failed to create timer task")?;
|
||||||
|
|
||||||
|
self.delay_timer
|
||||||
|
.add_task(task)
|
||||||
|
.context("failed to add timer task")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the task runner
|
||||||
|
async fn async_task(core: Core, uid: String) {
|
||||||
|
log::info!(target: "app", "running timer task `{uid}`");
|
||||||
|
log_if_err!(core.update_profile_item(uid, None).await);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum DiffFlag {
|
enum DiffFlag {
|
||||||
Del(TaskID),
|
Del(TaskID),
|
||||||
Add(TaskID, u64),
|
Add(TaskID, u64),
|
||||||
Mod(TaskID, u64),
|
Mod(TaskID, u64),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,124 +1,130 @@
|
||||||
use crate::{data::Data, feat, utils::resolve};
|
use crate::{data::Data, feat, utils::resolve};
|
||||||
use anyhow::{Ok, Result};
|
use anyhow::{Ok, Result};
|
||||||
use tauri::{
|
use tauri::{
|
||||||
api, AppHandle, CustomMenuItem, Manager, SystemTrayEvent, SystemTrayMenu, SystemTrayMenuItem,
|
api, AppHandle, CustomMenuItem, Manager, SystemTrayEvent, SystemTrayMenu, SystemTrayMenuItem,
|
||||||
SystemTraySubmenu,
|
SystemTraySubmenu,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Tray {}
|
pub struct Tray {}
|
||||||
|
|
||||||
impl Tray {
|
impl Tray {
|
||||||
pub fn tray_menu(app_handle: &AppHandle) -> SystemTrayMenu {
|
pub fn tray_menu(app_handle: &AppHandle) -> SystemTrayMenu {
|
||||||
let data = Data::global();
|
let data = Data::global();
|
||||||
let zh = {
|
let zh = {
|
||||||
let verge = data.verge.lock();
|
let verge = data.verge.lock();
|
||||||
verge.language == Some("zh".into())
|
verge.language == Some("zh".into())
|
||||||
};
|
};
|
||||||
|
|
||||||
let version = app_handle.package_info().version.to_string();
|
let version = app_handle.package_info().version.to_string();
|
||||||
|
|
||||||
if zh {
|
if zh {
|
||||||
SystemTrayMenu::new()
|
SystemTrayMenu::new()
|
||||||
.add_item(CustomMenuItem::new("open_window", "打开面板"))
|
.add_item(CustomMenuItem::new("open_window", "打开面板"))
|
||||||
.add_native_item(SystemTrayMenuItem::Separator)
|
.add_native_item(SystemTrayMenuItem::Separator)
|
||||||
.add_item(CustomMenuItem::new("rule_mode", "规则模式"))
|
.add_item(CustomMenuItem::new("rule_mode", "规则模式"))
|
||||||
.add_item(CustomMenuItem::new("global_mode", "全局模式"))
|
.add_item(CustomMenuItem::new("global_mode", "全局模式"))
|
||||||
.add_item(CustomMenuItem::new("direct_mode", "直连模式"))
|
.add_item(CustomMenuItem::new("direct_mode", "直连模式"))
|
||||||
.add_item(CustomMenuItem::new("script_mode", "脚本模式"))
|
.add_item(CustomMenuItem::new("script_mode", "脚本模式"))
|
||||||
.add_native_item(SystemTrayMenuItem::Separator)
|
.add_native_item(SystemTrayMenuItem::Separator)
|
||||||
.add_item(CustomMenuItem::new("system_proxy", "系统代理"))
|
.add_item(CustomMenuItem::new("system_proxy", "系统代理"))
|
||||||
.add_item(CustomMenuItem::new("tun_mode", "TUN 模式"))
|
.add_item(CustomMenuItem::new("tun_mode", "TUN 模式"))
|
||||||
.add_submenu(SystemTraySubmenu::new(
|
.add_submenu(SystemTraySubmenu::new(
|
||||||
"更多",
|
"更多",
|
||||||
SystemTrayMenu::new()
|
SystemTrayMenu::new()
|
||||||
.add_item(CustomMenuItem::new("restart_clash", "重启 Clash"))
|
.add_item(CustomMenuItem::new("restart_clash", "重启 Clash"))
|
||||||
.add_item(CustomMenuItem::new("restart_app", "重启应用"))
|
.add_item(CustomMenuItem::new("restart_app", "重启应用"))
|
||||||
.add_item(CustomMenuItem::new("app_version", format!("Version {version}")).disabled()),
|
.add_item(
|
||||||
))
|
CustomMenuItem::new("app_version", format!("Version {version}"))
|
||||||
.add_native_item(SystemTrayMenuItem::Separator)
|
.disabled(),
|
||||||
.add_item(CustomMenuItem::new("quit", "退出").accelerator("CmdOrControl+Q"))
|
),
|
||||||
} else {
|
))
|
||||||
SystemTrayMenu::new()
|
.add_native_item(SystemTrayMenuItem::Separator)
|
||||||
.add_item(CustomMenuItem::new("open_window", "Dashboard"))
|
.add_item(CustomMenuItem::new("quit", "退出").accelerator("CmdOrControl+Q"))
|
||||||
.add_native_item(SystemTrayMenuItem::Separator)
|
} else {
|
||||||
.add_item(CustomMenuItem::new("rule_mode", "Rule Mode"))
|
SystemTrayMenu::new()
|
||||||
.add_item(CustomMenuItem::new("global_mode", "Global Mode"))
|
.add_item(CustomMenuItem::new("open_window", "Dashboard"))
|
||||||
.add_item(CustomMenuItem::new("direct_mode", "Direct Mode"))
|
.add_native_item(SystemTrayMenuItem::Separator)
|
||||||
.add_item(CustomMenuItem::new("script_mode", "Script Mode"))
|
.add_item(CustomMenuItem::new("rule_mode", "Rule Mode"))
|
||||||
.add_native_item(SystemTrayMenuItem::Separator)
|
.add_item(CustomMenuItem::new("global_mode", "Global Mode"))
|
||||||
.add_item(CustomMenuItem::new("system_proxy", "System Proxy"))
|
.add_item(CustomMenuItem::new("direct_mode", "Direct Mode"))
|
||||||
.add_item(CustomMenuItem::new("tun_mode", "Tun Mode"))
|
.add_item(CustomMenuItem::new("script_mode", "Script Mode"))
|
||||||
.add_submenu(SystemTraySubmenu::new(
|
.add_native_item(SystemTrayMenuItem::Separator)
|
||||||
"More",
|
.add_item(CustomMenuItem::new("system_proxy", "System Proxy"))
|
||||||
SystemTrayMenu::new()
|
.add_item(CustomMenuItem::new("tun_mode", "Tun Mode"))
|
||||||
.add_item(CustomMenuItem::new("restart_clash", "Restart Clash"))
|
.add_submenu(SystemTraySubmenu::new(
|
||||||
.add_item(CustomMenuItem::new("restart_app", "Restart App"))
|
"More",
|
||||||
.add_item(CustomMenuItem::new("app_version", format!("Version {version}")).disabled()),
|
SystemTrayMenu::new()
|
||||||
))
|
.add_item(CustomMenuItem::new("restart_clash", "Restart Clash"))
|
||||||
.add_native_item(SystemTrayMenuItem::Separator)
|
.add_item(CustomMenuItem::new("restart_app", "Restart App"))
|
||||||
.add_item(CustomMenuItem::new("quit", "Quit").accelerator("CmdOrControl+Q"))
|
.add_item(
|
||||||
}
|
CustomMenuItem::new("app_version", format!("Version {version}"))
|
||||||
}
|
.disabled(),
|
||||||
|
),
|
||||||
pub fn update_systray(app_handle: &AppHandle) -> Result<()> {
|
))
|
||||||
app_handle
|
.add_native_item(SystemTrayMenuItem::Separator)
|
||||||
.tray_handle()
|
.add_item(CustomMenuItem::new("quit", "Quit").accelerator("CmdOrControl+Q"))
|
||||||
.set_menu(Tray::tray_menu(app_handle))?;
|
}
|
||||||
Tray::update_part(app_handle)?;
|
}
|
||||||
Ok(())
|
|
||||||
}
|
pub fn update_systray(app_handle: &AppHandle) -> Result<()> {
|
||||||
|
app_handle
|
||||||
pub fn update_part(app_handle: &AppHandle) -> Result<()> {
|
.tray_handle()
|
||||||
let global = Data::global();
|
.set_menu(Tray::tray_menu(app_handle))?;
|
||||||
let clash = global.clash.lock();
|
Tray::update_part(app_handle)?;
|
||||||
let mode = clash
|
Ok(())
|
||||||
.config
|
}
|
||||||
.get(&serde_yaml::Value::from("mode"))
|
|
||||||
.map(|val| val.as_str().unwrap_or("rule"))
|
pub fn update_part(app_handle: &AppHandle) -> Result<()> {
|
||||||
.unwrap_or("rule");
|
let global = Data::global();
|
||||||
|
let clash = global.clash.lock();
|
||||||
let tray = app_handle.tray_handle();
|
let mode = clash
|
||||||
|
.config
|
||||||
let _ = tray.get_item("rule_mode").set_selected(mode == "rule");
|
.get(&serde_yaml::Value::from("mode"))
|
||||||
let _ = tray.get_item("global_mode").set_selected(mode == "global");
|
.map(|val| val.as_str().unwrap_or("rule"))
|
||||||
let _ = tray.get_item("direct_mode").set_selected(mode == "direct");
|
.unwrap_or("rule");
|
||||||
let _ = tray.get_item("script_mode").set_selected(mode == "script");
|
|
||||||
|
let tray = app_handle.tray_handle();
|
||||||
let verge = global.verge.lock();
|
|
||||||
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
|
let _ = tray.get_item("rule_mode").set_selected(mode == "rule");
|
||||||
let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
|
let _ = tray.get_item("global_mode").set_selected(mode == "global");
|
||||||
|
let _ = tray.get_item("direct_mode").set_selected(mode == "direct");
|
||||||
let _ = tray.get_item("system_proxy").set_selected(*system_proxy);
|
let _ = tray.get_item("script_mode").set_selected(mode == "script");
|
||||||
let _ = tray.get_item("tun_mode").set_selected(*tun_mode);
|
|
||||||
|
let verge = global.verge.lock();
|
||||||
Ok(())
|
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
|
||||||
}
|
let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
|
||||||
|
|
||||||
pub fn on_system_tray_event(app_handle: &AppHandle, event: SystemTrayEvent) {
|
let _ = tray.get_item("system_proxy").set_selected(*system_proxy);
|
||||||
match event {
|
let _ = tray.get_item("tun_mode").set_selected(*tun_mode);
|
||||||
SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() {
|
|
||||||
mode @ ("rule_mode" | "global_mode" | "direct_mode" | "script_mode") => {
|
Ok(())
|
||||||
let mode = &mode[0..mode.len() - 5];
|
}
|
||||||
feat::change_clash_mode(mode);
|
|
||||||
|
pub fn on_system_tray_event(app_handle: &AppHandle, event: SystemTrayEvent) {
|
||||||
|
match event {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
"open_window" => resolve::create_window(app_handle),
|
||||||
|
"system_proxy" => feat::toggle_system_proxy(),
|
||||||
|
"tun_mode" => feat::toggle_tun_mode(),
|
||||||
|
"restart_clash" => feat::restart_clash_core(),
|
||||||
|
"restart_app" => api::process::restart(&app_handle.env()),
|
||||||
|
"quit" => {
|
||||||
|
resolve::resolve_reset();
|
||||||
|
api::process::kill_children();
|
||||||
|
app_handle.exit(0);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
SystemTrayEvent::LeftClick { .. } => {
|
||||||
|
resolve::create_window(app_handle);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
"open_window" => resolve::create_window(app_handle),
|
|
||||||
"system_proxy" => feat::toggle_system_proxy(),
|
|
||||||
"tun_mode" => feat::toggle_tun_mode(),
|
|
||||||
"restart_clash" => feat::restart_clash_core(),
|
|
||||||
"restart_app" => api::process::restart(&app_handle.env()),
|
|
||||||
"quit" => {
|
|
||||||
resolve::resolve_reset();
|
|
||||||
api::process::kill_children();
|
|
||||||
app_handle.exit(0);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
},
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
SystemTrayEvent::LeftClick { .. } => {
|
|
||||||
resolve::create_window(app_handle);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,163 +5,163 @@ use serde_yaml::{Mapping, Value};
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct ClashInfo {
|
pub struct ClashInfo {
|
||||||
/// clash sidecar status
|
/// clash sidecar status
|
||||||
pub status: String,
|
pub status: String,
|
||||||
|
|
||||||
/// clash core port
|
/// clash core port
|
||||||
pub port: Option<String>,
|
pub port: Option<String>,
|
||||||
|
|
||||||
/// same as `external-controller`
|
/// same as `external-controller`
|
||||||
pub server: Option<String>,
|
pub server: Option<String>,
|
||||||
|
|
||||||
/// clash secret
|
/// clash secret
|
||||||
pub secret: Option<String>,
|
pub secret: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClashInfo {
|
impl ClashInfo {
|
||||||
/// parse the clash's config.yaml
|
/// parse the clash's config.yaml
|
||||||
/// get some information
|
/// get some information
|
||||||
pub fn from(config: &Mapping) -> ClashInfo {
|
pub fn from(config: &Mapping) -> ClashInfo {
|
||||||
let key_port_1 = Value::from("mixed-port");
|
let key_port_1 = Value::from("mixed-port");
|
||||||
let key_port_2 = Value::from("port");
|
let key_port_2 = Value::from("port");
|
||||||
let key_server = Value::from("external-controller");
|
let key_server = Value::from("external-controller");
|
||||||
let key_secret = Value::from("secret");
|
let key_secret = Value::from("secret");
|
||||||
|
|
||||||
let mut status: u32 = 0;
|
let mut status: u32 = 0;
|
||||||
|
|
||||||
let port = match config.get(&key_port_1) {
|
let port = match config.get(&key_port_1) {
|
||||||
Some(value) => match value {
|
Some(value) => match value {
|
||||||
Value::String(val_str) => Some(val_str.clone()),
|
Value::String(val_str) => Some(val_str.clone()),
|
||||||
Value::Number(val_num) => Some(val_num.to_string()),
|
Value::Number(val_num) => Some(val_num.to_string()),
|
||||||
_ => {
|
_ => {
|
||||||
status |= 0b1;
|
status |= 0b1;
|
||||||
None
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
status |= 0b10;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let port = match port {
|
||||||
|
Some(_) => port,
|
||||||
|
None => match config.get(&key_port_2) {
|
||||||
|
Some(value) => match value {
|
||||||
|
Value::String(val_str) => Some(val_str.clone()),
|
||||||
|
Value::Number(val_num) => Some(val_num.to_string()),
|
||||||
|
_ => {
|
||||||
|
status |= 0b100;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
status |= 0b1000;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// `external-controller` could be
|
||||||
|
// "127.0.0.1:9090" or ":9090"
|
||||||
|
let server = match config.get(&key_server) {
|
||||||
|
Some(value) => match value.as_str() {
|
||||||
|
Some(val_str) => {
|
||||||
|
if val_str.starts_with(":") {
|
||||||
|
Some(format!("127.0.0.1{val_str}"))
|
||||||
|
} else if val_str.starts_with("0.0.0.0:") {
|
||||||
|
Some(format!("127.0.0.1:{}", &val_str[8..]))
|
||||||
|
} else if val_str.starts_with("[::]:") {
|
||||||
|
Some(format!("127.0.0.1:{}", &val_str[5..]))
|
||||||
|
} else {
|
||||||
|
Some(val_str.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
status |= 0b10000;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
status |= 0b100000;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let secret = match config.get(&key_secret) {
|
||||||
|
Some(value) => match value {
|
||||||
|
Value::String(val_str) => Some(val_str.clone()),
|
||||||
|
Value::Bool(val_bool) => Some(val_bool.to_string()),
|
||||||
|
Value::Number(val_num) => Some(val_num.to_string()),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
ClashInfo {
|
||||||
|
status: format!("{status}"),
|
||||||
|
port,
|
||||||
|
server,
|
||||||
|
secret,
|
||||||
}
|
}
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
status |= 0b10;
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let port = match port {
|
|
||||||
Some(_) => port,
|
|
||||||
None => match config.get(&key_port_2) {
|
|
||||||
Some(value) => match value {
|
|
||||||
Value::String(val_str) => Some(val_str.clone()),
|
|
||||||
Value::Number(val_num) => Some(val_num.to_string()),
|
|
||||||
_ => {
|
|
||||||
status |= 0b100;
|
|
||||||
None
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
status |= 0b1000;
|
|
||||||
None
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// `external-controller` could be
|
|
||||||
// "127.0.0.1:9090" or ":9090"
|
|
||||||
let server = match config.get(&key_server) {
|
|
||||||
Some(value) => match value.as_str() {
|
|
||||||
Some(val_str) => {
|
|
||||||
if val_str.starts_with(":") {
|
|
||||||
Some(format!("127.0.0.1{val_str}"))
|
|
||||||
} else if val_str.starts_with("0.0.0.0:") {
|
|
||||||
Some(format!("127.0.0.1:{}", &val_str[8..]))
|
|
||||||
} else if val_str.starts_with("[::]:") {
|
|
||||||
Some(format!("127.0.0.1:{}", &val_str[5..]))
|
|
||||||
} else {
|
|
||||||
Some(val_str.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
status |= 0b10000;
|
|
||||||
None
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => {
|
|
||||||
status |= 0b100000;
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let secret = match config.get(&key_secret) {
|
|
||||||
Some(value) => match value {
|
|
||||||
Value::String(val_str) => Some(val_str.clone()),
|
|
||||||
Value::Bool(val_bool) => Some(val_bool.to_string()),
|
|
||||||
Value::Number(val_num) => Some(val_num.to_string()),
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
ClashInfo {
|
|
||||||
status: format!("{status}"),
|
|
||||||
port,
|
|
||||||
server,
|
|
||||||
secret,
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Clash {
|
pub struct Clash {
|
||||||
/// maintain the clash config
|
/// maintain the clash config
|
||||||
pub config: Mapping,
|
pub config: Mapping,
|
||||||
|
|
||||||
/// some info
|
/// some info
|
||||||
pub info: ClashInfo,
|
pub info: ClashInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clash {
|
impl Clash {
|
||||||
pub fn new() -> Clash {
|
pub fn new() -> Clash {
|
||||||
let config = Clash::read_config();
|
let config = Clash::read_config();
|
||||||
let info = ClashInfo::from(&config);
|
let info = ClashInfo::from(&config);
|
||||||
|
|
||||||
Clash { config, info }
|
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 {
|
/// get clash config
|
||||||
self.info = ClashInfo::from(&self.config);
|
pub fn read_config() -> Mapping {
|
||||||
|
config::read_merge_mapping(dirs::clash_path())
|
||||||
}
|
}
|
||||||
|
|
||||||
self.save_config()
|
/// 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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Clash {
|
impl Default for Clash {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Clash::new()
|
Clash::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,19 +14,19 @@ use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Data {
|
pub struct Data {
|
||||||
pub clash: Arc<Mutex<Clash>>,
|
pub clash: Arc<Mutex<Clash>>,
|
||||||
pub verge: Arc<Mutex<Verge>>,
|
pub verge: Arc<Mutex<Verge>>,
|
||||||
pub profiles: Arc<Mutex<Profiles>>,
|
pub profiles: Arc<Mutex<Profiles>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Data {
|
impl Data {
|
||||||
pub fn global() -> &'static Data {
|
pub fn global() -> &'static Data {
|
||||||
static DATA: OnceCell<Data> = OnceCell::new();
|
static DATA: OnceCell<Data> = OnceCell::new();
|
||||||
|
|
||||||
DATA.get_or_init(|| Data {
|
DATA.get_or_init(|| Data {
|
||||||
clash: Arc::new(Mutex::new(Clash::new())),
|
clash: Arc::new(Mutex::new(Clash::new())),
|
||||||
verge: Arc::new(Mutex::new(Verge::new())),
|
verge: Arc::new(Mutex::new(Verge::new())),
|
||||||
profiles: Arc::new(Mutex::new(Profiles::new())),
|
profiles: Arc::new(Mutex::new(Profiles::new())),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,399 +8,399 @@ use sysproxy::Sysproxy;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct PrfItem {
|
pub struct PrfItem {
|
||||||
pub uid: Option<String>,
|
pub uid: Option<String>,
|
||||||
|
|
||||||
/// profile item type
|
/// profile item type
|
||||||
/// enum value: remote | local | script | merge
|
/// enum value: remote | local | script | merge
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub itype: Option<String>,
|
pub itype: Option<String>,
|
||||||
|
|
||||||
/// profile name
|
/// profile name
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
|
|
||||||
/// profile file
|
/// profile file
|
||||||
pub file: Option<String>,
|
pub file: Option<String>,
|
||||||
|
|
||||||
/// profile description
|
/// profile description
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub desc: Option<String>,
|
pub desc: Option<String>,
|
||||||
|
|
||||||
/// source url
|
/// source url
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub url: Option<String>,
|
pub url: Option<String>,
|
||||||
|
|
||||||
/// selected infomation
|
/// selected infomation
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub selected: Option<Vec<PrfSelected>>,
|
pub selected: Option<Vec<PrfSelected>>,
|
||||||
|
|
||||||
/// subscription user info
|
/// subscription user info
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub extra: Option<PrfExtra>,
|
pub extra: Option<PrfExtra>,
|
||||||
|
|
||||||
/// updated time
|
/// updated time
|
||||||
pub updated: Option<usize>,
|
pub updated: Option<usize>,
|
||||||
|
|
||||||
/// some options of the item
|
/// some options of the item
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub option: Option<PrfOption>,
|
pub option: Option<PrfOption>,
|
||||||
|
|
||||||
/// the file data
|
/// the file data
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub file_data: Option<String>,
|
pub file_data: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct PrfSelected {
|
pub struct PrfSelected {
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub now: Option<String>,
|
pub now: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Copy, Deserialize, Serialize)]
|
#[derive(Default, Debug, Clone, Copy, Deserialize, Serialize)]
|
||||||
pub struct PrfExtra {
|
pub struct PrfExtra {
|
||||||
pub upload: usize,
|
pub upload: usize,
|
||||||
pub download: usize,
|
pub download: usize,
|
||||||
pub total: usize,
|
pub total: usize,
|
||||||
pub expire: usize,
|
pub expire: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
pub struct PrfOption {
|
pub struct PrfOption {
|
||||||
/// for `remote` profile's http request
|
/// for `remote` profile's http request
|
||||||
/// see issue #13
|
/// see issue #13
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub user_agent: Option<String>,
|
pub user_agent: Option<String>,
|
||||||
|
|
||||||
/// for `remote` profile
|
/// for `remote` profile
|
||||||
/// use system proxy
|
/// use system proxy
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub with_proxy: Option<bool>,
|
pub with_proxy: Option<bool>,
|
||||||
|
|
||||||
/// for `remote` profile
|
/// for `remote` profile
|
||||||
/// use self proxy
|
/// use self proxy
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub self_proxy: Option<bool>,
|
pub self_proxy: Option<bool>,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub update_interval: Option<u64>,
|
pub update_interval: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrfOption {
|
impl PrfOption {
|
||||||
pub fn merge(one: Option<Self>, other: Option<Self>) -> Option<Self> {
|
pub fn merge(one: Option<Self>, other: Option<Self>) -> Option<Self> {
|
||||||
match (one, other) {
|
match (one, other) {
|
||||||
(Some(mut a), Some(b)) => {
|
(Some(mut a), Some(b)) => {
|
||||||
a.user_agent = b.user_agent.or(a.user_agent);
|
a.user_agent = b.user_agent.or(a.user_agent);
|
||||||
a.with_proxy = b.with_proxy.or(a.with_proxy);
|
a.with_proxy = b.with_proxy.or(a.with_proxy);
|
||||||
a.self_proxy = b.self_proxy.or(a.self_proxy);
|
a.self_proxy = b.self_proxy.or(a.self_proxy);
|
||||||
a.update_interval = b.update_interval.or(a.update_interval);
|
a.update_interval = b.update_interval.or(a.update_interval);
|
||||||
Some(a)
|
Some(a)
|
||||||
}
|
}
|
||||||
t @ _ => t.0.or(t.1),
|
t @ _ => t.0.or(t.1),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PrfItem {
|
impl Default for PrfItem {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
PrfItem {
|
PrfItem {
|
||||||
uid: None,
|
uid: None,
|
||||||
itype: None,
|
itype: None,
|
||||||
name: None,
|
name: None,
|
||||||
desc: None,
|
desc: None,
|
||||||
file: None,
|
file: None,
|
||||||
url: None,
|
url: None,
|
||||||
selected: None,
|
selected: None,
|
||||||
extra: None,
|
extra: None,
|
||||||
updated: None,
|
updated: None,
|
||||||
option: None,
|
option: None,
|
||||||
file_data: None,
|
file_data: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrfItem {
|
impl PrfItem {
|
||||||
/// From partial item
|
/// From partial item
|
||||||
/// must contain `itype`
|
/// must contain `itype`
|
||||||
pub async fn from(item: PrfItem, file_data: Option<String>) -> Result<PrfItem> {
|
pub async fn from(item: PrfItem, file_data: Option<String>) -> Result<PrfItem> {
|
||||||
if item.itype.is_none() {
|
if item.itype.is_none() {
|
||||||
bail!("type should not be null");
|
bail!("type should not be null");
|
||||||
}
|
|
||||||
|
|
||||||
match item.itype.unwrap().as_str() {
|
|
||||||
"remote" => {
|
|
||||||
if item.url.is_none() {
|
|
||||||
bail!("url should not be null");
|
|
||||||
}
|
}
|
||||||
let url = item.url.as_ref().unwrap().as_str();
|
|
||||||
let name = item.name;
|
|
||||||
let desc = item.desc;
|
|
||||||
PrfItem::from_url(url, name, desc, item.option).await
|
|
||||||
}
|
|
||||||
"local" => {
|
|
||||||
let name = item.name.unwrap_or("Local File".into());
|
|
||||||
let desc = item.desc.unwrap_or("".into());
|
|
||||||
PrfItem::from_local(name, desc, file_data)
|
|
||||||
}
|
|
||||||
"merge" => {
|
|
||||||
let name = item.name.unwrap_or("Merge".into());
|
|
||||||
let desc = item.desc.unwrap_or("".into());
|
|
||||||
PrfItem::from_merge(name, desc)
|
|
||||||
}
|
|
||||||
"script" => {
|
|
||||||
let name = item.name.unwrap_or("Script".into());
|
|
||||||
let desc = item.desc.unwrap_or("".into());
|
|
||||||
PrfItem::from_script(name, desc)
|
|
||||||
}
|
|
||||||
typ @ _ => bail!("invalid profile item type \"{typ}\""),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ## Local type
|
match item.itype.unwrap().as_str() {
|
||||||
/// create a new item from name/desc
|
"remote" => {
|
||||||
pub fn from_local(name: String, desc: String, file_data: Option<String>) -> Result<PrfItem> {
|
if item.url.is_none() {
|
||||||
let uid = help::get_uid("l");
|
bail!("url should not be null");
|
||||||
let file = format!("{uid}.yaml");
|
}
|
||||||
|
let url = item.url.as_ref().unwrap().as_str();
|
||||||
Ok(PrfItem {
|
let name = item.name;
|
||||||
uid: Some(uid),
|
let desc = item.desc;
|
||||||
itype: Some("local".into()),
|
PrfItem::from_url(url, name, desc, item.option).await
|
||||||
name: Some(name),
|
}
|
||||||
desc: Some(desc),
|
"local" => {
|
||||||
file: Some(file),
|
let name = item.name.unwrap_or("Local File".into());
|
||||||
url: None,
|
let desc = item.desc.unwrap_or("".into());
|
||||||
selected: None,
|
PrfItem::from_local(name, desc, file_data)
|
||||||
extra: None,
|
}
|
||||||
option: None,
|
"merge" => {
|
||||||
updated: Some(help::get_now()),
|
let name = item.name.unwrap_or("Merge".into());
|
||||||
file_data: Some(file_data.unwrap_or(tmpl::ITEM_LOCAL.into())),
|
let desc = item.desc.unwrap_or("".into());
|
||||||
})
|
PrfItem::from_merge(name, desc)
|
||||||
}
|
}
|
||||||
|
"script" => {
|
||||||
/// ## Remote type
|
let name = item.name.unwrap_or("Script".into());
|
||||||
/// create a new item from url
|
let desc = item.desc.unwrap_or("".into());
|
||||||
pub async fn from_url(
|
PrfItem::from_script(name, desc)
|
||||||
url: &str,
|
}
|
||||||
name: Option<String>,
|
typ @ _ => bail!("invalid profile item type \"{typ}\""),
|
||||||
desc: Option<String>,
|
|
||||||
option: Option<PrfOption>,
|
|
||||||
) -> Result<PrfItem> {
|
|
||||||
let opt_ref = option.as_ref();
|
|
||||||
let with_proxy = opt_ref.map_or(false, |o| o.with_proxy.unwrap_or(false));
|
|
||||||
let self_proxy = opt_ref.map_or(false, |o| o.self_proxy.unwrap_or(false));
|
|
||||||
let user_agent = opt_ref.map_or(None, |o| o.user_agent.clone());
|
|
||||||
|
|
||||||
let mut builder = reqwest::ClientBuilder::new().no_proxy();
|
|
||||||
|
|
||||||
// 使用软件自己的代理
|
|
||||||
if self_proxy {
|
|
||||||
let data = super::Data::global();
|
|
||||||
let port = data.clash.lock().info.port.clone();
|
|
||||||
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) {
|
|
||||||
builder = builder.proxy(proxy);
|
|
||||||
}
|
|
||||||
if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) {
|
|
||||||
builder = builder.proxy(proxy);
|
|
||||||
}
|
|
||||||
if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) {
|
|
||||||
builder = builder.proxy(proxy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 使用系统代理
|
|
||||||
else if with_proxy {
|
|
||||||
match Sysproxy::get_system_proxy() {
|
|
||||||
Ok(p @ Sysproxy { enable: true, .. }) => {
|
|
||||||
let proxy_scheme = format!("http://{}:{}", p.host, p.port);
|
|
||||||
|
|
||||||
if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) {
|
|
||||||
builder = builder.proxy(proxy);
|
|
||||||
}
|
|
||||||
if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) {
|
|
||||||
builder = builder.proxy(proxy);
|
|
||||||
}
|
|
||||||
if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) {
|
|
||||||
builder = builder.proxy(proxy);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let version = unsafe { dirs::APP_VERSION };
|
/// ## Local type
|
||||||
let version = format!("clash-verge/{version}");
|
/// create a new item from name/desc
|
||||||
builder = builder.user_agent(user_agent.unwrap_or(version));
|
pub fn from_local(name: String, desc: String, file_data: Option<String>) -> Result<PrfItem> {
|
||||||
|
let uid = help::get_uid("l");
|
||||||
|
let file = format!("{uid}.yaml");
|
||||||
|
|
||||||
let resp = builder.build()?.get(url).send().await?;
|
Ok(PrfItem {
|
||||||
|
uid: Some(uid),
|
||||||
let status_code = resp.status();
|
itype: Some("local".into()),
|
||||||
if !StatusCode::is_success(&status_code) {
|
name: Some(name),
|
||||||
bail!("failed to fetch remote profile with status {status_code}")
|
desc: Some(desc),
|
||||||
}
|
file: Some(file),
|
||||||
|
url: None,
|
||||||
let header = resp.headers();
|
selected: None,
|
||||||
|
extra: None,
|
||||||
// parse the Subscription UserInfo
|
option: None,
|
||||||
let extra = match header.get("Subscription-Userinfo") {
|
updated: Some(help::get_now()),
|
||||||
Some(value) => {
|
file_data: Some(file_data.unwrap_or(tmpl::ITEM_LOCAL.into())),
|
||||||
let sub_info = value.to_str().unwrap_or("");
|
|
||||||
|
|
||||||
Some(PrfExtra {
|
|
||||||
upload: help::parse_str(sub_info, "upload=").unwrap_or(0),
|
|
||||||
download: help::parse_str(sub_info, "download=").unwrap_or(0),
|
|
||||||
total: help::parse_str(sub_info, "total=").unwrap_or(0),
|
|
||||||
expire: help::parse_str(sub_info, "expire=").unwrap_or(0),
|
|
||||||
})
|
})
|
||||||
}
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
// parse the Content-Disposition
|
|
||||||
let filename = match header.get("Content-Disposition") {
|
|
||||||
Some(value) => {
|
|
||||||
let filename = value.to_str().unwrap_or("");
|
|
||||||
help::parse_str::<String>(filename, "filename=")
|
|
||||||
}
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
// parse the profile-update-interval
|
|
||||||
let option = match header.get("profile-update-interval") {
|
|
||||||
Some(value) => match value.to_str().unwrap_or("").parse::<u64>() {
|
|
||||||
Ok(val) => Some(PrfOption {
|
|
||||||
update_interval: Some(val * 60), // hour -> min
|
|
||||||
..PrfOption::default()
|
|
||||||
}),
|
|
||||||
Err(_) => None,
|
|
||||||
},
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let uid = help::get_uid("r");
|
|
||||||
let file = format!("{uid}.yaml");
|
|
||||||
let name = name.unwrap_or(filename.unwrap_or("Remote File".into()));
|
|
||||||
let data = resp.text_with_charset("utf-8").await?;
|
|
||||||
|
|
||||||
// check the data whether the valid yaml format
|
|
||||||
let yaml = serde_yaml::from_str::<Mapping>(&data) //
|
|
||||||
.context("the remote profile data is invalid yaml")?;
|
|
||||||
|
|
||||||
if !yaml.contains_key("proxies") && !yaml.contains_key("proxy-providers") {
|
|
||||||
bail!("profile does not contain `proxies` or `proxy-providers`");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(PrfItem {
|
/// ## Remote type
|
||||||
uid: Some(uid),
|
/// create a new item from url
|
||||||
itype: Some("remote".into()),
|
pub async fn from_url(
|
||||||
name: Some(name),
|
url: &str,
|
||||||
desc,
|
name: Option<String>,
|
||||||
file: Some(file),
|
desc: Option<String>,
|
||||||
url: Some(url.into()),
|
option: Option<PrfOption>,
|
||||||
selected: None,
|
) -> Result<PrfItem> {
|
||||||
extra,
|
let opt_ref = option.as_ref();
|
||||||
option,
|
let with_proxy = opt_ref.map_or(false, |o| o.with_proxy.unwrap_or(false));
|
||||||
updated: Some(help::get_now()),
|
let self_proxy = opt_ref.map_or(false, |o| o.self_proxy.unwrap_or(false));
|
||||||
file_data: Some(data),
|
let user_agent = opt_ref.map_or(None, |o| o.user_agent.clone());
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ## Merge type (enhance)
|
let mut builder = reqwest::ClientBuilder::new().no_proxy();
|
||||||
/// create the enhanced item by using `merge` rule
|
|
||||||
pub fn from_merge(name: String, desc: String) -> Result<PrfItem> {
|
|
||||||
let uid = help::get_uid("m");
|
|
||||||
let file = format!("{uid}.yaml");
|
|
||||||
|
|
||||||
Ok(PrfItem {
|
// 使用软件自己的代理
|
||||||
uid: Some(uid),
|
if self_proxy {
|
||||||
itype: Some("merge".into()),
|
let data = super::Data::global();
|
||||||
name: Some(name),
|
let port = data.clash.lock().info.port.clone();
|
||||||
desc: Some(desc),
|
let port = port.ok_or(anyhow::anyhow!("failed to get clash info port"))?;
|
||||||
file: Some(file),
|
let proxy_scheme = format!("http://127.0.0.1:{port}");
|
||||||
url: None,
|
|
||||||
selected: None,
|
|
||||||
extra: None,
|
|
||||||
option: None,
|
|
||||||
updated: Some(help::get_now()),
|
|
||||||
file_data: Some(tmpl::ITEM_MERGE.into()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ## Script type (enhance)
|
if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) {
|
||||||
/// create the enhanced item by using javascript(browserjs)
|
builder = builder.proxy(proxy);
|
||||||
pub fn from_script(name: String, desc: String) -> Result<PrfItem> {
|
}
|
||||||
let uid = help::get_uid("s");
|
if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) {
|
||||||
let file = format!("{uid}.js"); // js ext
|
builder = builder.proxy(proxy);
|
||||||
|
}
|
||||||
|
if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) {
|
||||||
|
builder = builder.proxy(proxy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 使用系统代理
|
||||||
|
else if with_proxy {
|
||||||
|
match Sysproxy::get_system_proxy() {
|
||||||
|
Ok(p @ Sysproxy { enable: true, .. }) => {
|
||||||
|
let proxy_scheme = format!("http://{}:{}", p.host, p.port);
|
||||||
|
|
||||||
Ok(PrfItem {
|
if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) {
|
||||||
uid: Some(uid),
|
builder = builder.proxy(proxy);
|
||||||
itype: Some("script".into()),
|
}
|
||||||
name: Some(name),
|
if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) {
|
||||||
desc: Some(desc),
|
builder = builder.proxy(proxy);
|
||||||
file: Some(file),
|
}
|
||||||
url: None,
|
if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) {
|
||||||
selected: None,
|
builder = builder.proxy(proxy);
|
||||||
extra: None,
|
}
|
||||||
option: None,
|
}
|
||||||
updated: Some(help::get_now()),
|
_ => {}
|
||||||
file_data: Some(tmpl::ITEM_SCRIPT.into()),
|
};
|
||||||
})
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// get the file data
|
let version = unsafe { dirs::APP_VERSION };
|
||||||
pub fn read_file(&self) -> Result<String> {
|
let version = format!("clash-verge/{version}");
|
||||||
if self.file.is_none() {
|
builder = builder.user_agent(user_agent.unwrap_or(version));
|
||||||
bail!("could not find the file");
|
|
||||||
|
let resp = builder.build()?.get(url).send().await?;
|
||||||
|
|
||||||
|
let status_code = resp.status();
|
||||||
|
if !StatusCode::is_success(&status_code) {
|
||||||
|
bail!("failed to fetch remote profile with status {status_code}")
|
||||||
|
}
|
||||||
|
|
||||||
|
let header = resp.headers();
|
||||||
|
|
||||||
|
// parse the Subscription UserInfo
|
||||||
|
let extra = match header.get("Subscription-Userinfo") {
|
||||||
|
Some(value) => {
|
||||||
|
let sub_info = value.to_str().unwrap_or("");
|
||||||
|
|
||||||
|
Some(PrfExtra {
|
||||||
|
upload: help::parse_str(sub_info, "upload=").unwrap_or(0),
|
||||||
|
download: help::parse_str(sub_info, "download=").unwrap_or(0),
|
||||||
|
total: help::parse_str(sub_info, "total=").unwrap_or(0),
|
||||||
|
expire: help::parse_str(sub_info, "expire=").unwrap_or(0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// parse the Content-Disposition
|
||||||
|
let filename = match header.get("Content-Disposition") {
|
||||||
|
Some(value) => {
|
||||||
|
let filename = value.to_str().unwrap_or("");
|
||||||
|
help::parse_str::<String>(filename, "filename=")
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// parse the profile-update-interval
|
||||||
|
let option = match header.get("profile-update-interval") {
|
||||||
|
Some(value) => match value.to_str().unwrap_or("").parse::<u64>() {
|
||||||
|
Ok(val) => Some(PrfOption {
|
||||||
|
update_interval: Some(val * 60), // hour -> min
|
||||||
|
..PrfOption::default()
|
||||||
|
}),
|
||||||
|
Err(_) => None,
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let uid = help::get_uid("r");
|
||||||
|
let file = format!("{uid}.yaml");
|
||||||
|
let name = name.unwrap_or(filename.unwrap_or("Remote File".into()));
|
||||||
|
let data = resp.text_with_charset("utf-8").await?;
|
||||||
|
|
||||||
|
// check the data whether the valid yaml format
|
||||||
|
let yaml = serde_yaml::from_str::<Mapping>(&data) //
|
||||||
|
.context("the remote profile data is invalid yaml")?;
|
||||||
|
|
||||||
|
if !yaml.contains_key("proxies") && !yaml.contains_key("proxy-providers") {
|
||||||
|
bail!("profile does not contain `proxies` or `proxy-providers`");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(PrfItem {
|
||||||
|
uid: Some(uid),
|
||||||
|
itype: Some("remote".into()),
|
||||||
|
name: Some(name),
|
||||||
|
desc,
|
||||||
|
file: Some(file),
|
||||||
|
url: Some(url.into()),
|
||||||
|
selected: None,
|
||||||
|
extra,
|
||||||
|
option,
|
||||||
|
updated: Some(help::get_now()),
|
||||||
|
file_data: Some(data),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let file = self.file.clone().unwrap();
|
/// ## Merge type (enhance)
|
||||||
let path = dirs::app_profiles_dir().join(file);
|
/// create the enhanced item by using `merge` rule
|
||||||
fs::read_to_string(path).context("failed to read the file")
|
pub fn from_merge(name: String, desc: String) -> Result<PrfItem> {
|
||||||
}
|
let uid = help::get_uid("m");
|
||||||
|
let file = format!("{uid}.yaml");
|
||||||
|
|
||||||
/// save the file data
|
Ok(PrfItem {
|
||||||
pub fn save_file(&self, data: String) -> Result<()> {
|
uid: Some(uid),
|
||||||
if self.file.is_none() {
|
itype: Some("merge".into()),
|
||||||
bail!("could not find the file");
|
name: Some(name),
|
||||||
|
desc: Some(desc),
|
||||||
|
file: Some(file),
|
||||||
|
url: None,
|
||||||
|
selected: None,
|
||||||
|
extra: None,
|
||||||
|
option: None,
|
||||||
|
updated: Some(help::get_now()),
|
||||||
|
file_data: Some(tmpl::ITEM_MERGE.into()),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let file = self.file.clone().unwrap();
|
/// ## Script type (enhance)
|
||||||
let path = dirs::app_profiles_dir().join(file);
|
/// create the enhanced item by using javascript(browserjs)
|
||||||
fs::write(path, data.as_bytes()).context("failed to save the file")
|
pub fn from_script(name: String, desc: String) -> Result<PrfItem> {
|
||||||
}
|
let uid = help::get_uid("s");
|
||||||
|
let file = format!("{uid}.js"); // js ext
|
||||||
|
|
||||||
/// get the data for enhanced mode
|
Ok(PrfItem {
|
||||||
pub fn to_enhance(&self) -> Option<ChainItem> {
|
uid: Some(uid),
|
||||||
let itype = self.itype.as_ref()?.as_str();
|
itype: Some("script".into()),
|
||||||
let file = self.file.clone()?;
|
name: Some(name),
|
||||||
let uid = self.uid.clone().unwrap_or("".into());
|
desc: Some(desc),
|
||||||
let path = dirs::app_profiles_dir().join(file);
|
file: Some(file),
|
||||||
|
url: None,
|
||||||
if !path.exists() {
|
selected: None,
|
||||||
return None;
|
extra: None,
|
||||||
|
option: None,
|
||||||
|
updated: Some(help::get_now()),
|
||||||
|
file_data: Some(tmpl::ITEM_SCRIPT.into()),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
match itype {
|
/// get the file data
|
||||||
"script" => Some(ChainItem {
|
pub fn read_file(&self) -> Result<String> {
|
||||||
uid,
|
if self.file.is_none() {
|
||||||
data: ChainType::Script(fs::read_to_string(path).unwrap_or("".into())),
|
bail!("could not find the file");
|
||||||
}),
|
}
|
||||||
"merge" => Some(ChainItem {
|
|
||||||
uid,
|
let file = self.file.clone().unwrap();
|
||||||
data: ChainType::Merge(config::read_merge_mapping(path)),
|
let path = dirs::app_profiles_dir().join(file);
|
||||||
}),
|
fs::read_to_string(path).context("failed to read the file")
|
||||||
_ => None,
|
}
|
||||||
|
|
||||||
|
/// save the file data
|
||||||
|
pub fn save_file(&self, data: String) -> Result<()> {
|
||||||
|
if self.file.is_none() {
|
||||||
|
bail!("could not find the file");
|
||||||
|
}
|
||||||
|
|
||||||
|
let file = self.file.clone().unwrap();
|
||||||
|
let path = dirs::app_profiles_dir().join(file);
|
||||||
|
fs::write(path, data.as_bytes()).context("failed to save the file")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get the data for enhanced mode
|
||||||
|
pub fn to_enhance(&self) -> Option<ChainItem> {
|
||||||
|
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);
|
||||||
|
|
||||||
|
if !path.exists() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
match itype {
|
||||||
|
"script" => Some(ChainItem {
|
||||||
|
uid,
|
||||||
|
data: ChainType::Script(fs::read_to_string(path).unwrap_or("".into())),
|
||||||
|
}),
|
||||||
|
"merge" => Some(ChainItem {
|
||||||
|
uid,
|
||||||
|
data: ChainType::Merge(config::read_merge_mapping(path)),
|
||||||
|
}),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ChainItem {
|
pub struct ChainItem {
|
||||||
pub uid: String,
|
pub uid: String,
|
||||||
pub data: ChainType,
|
pub data: ChainType,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum ChainType {
|
pub enum ChainType {
|
||||||
Merge(Mapping),
|
Merge(Mapping),
|
||||||
Script(String),
|
Script(String),
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,315 +14,316 @@ use std::{fs, io::Write};
|
||||||
///
|
///
|
||||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct Profiles {
|
pub struct Profiles {
|
||||||
/// same as PrfConfig.current
|
/// same as PrfConfig.current
|
||||||
current: Option<String>,
|
current: Option<String>,
|
||||||
|
|
||||||
/// same as PrfConfig.chain
|
/// same as PrfConfig.chain
|
||||||
chain: Option<Vec<String>>,
|
chain: Option<Vec<String>>,
|
||||||
|
|
||||||
/// record valid fields for clash
|
/// record valid fields for clash
|
||||||
valid: Option<Vec<String>>,
|
valid: Option<Vec<String>>,
|
||||||
|
|
||||||
/// profile list
|
/// profile list
|
||||||
items: Option<Vec<PrfItem>>,
|
items: Option<Vec<PrfItem>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! patch {
|
macro_rules! patch {
|
||||||
($lv: expr, $rv: expr, $key: tt) => {
|
($lv: expr, $rv: expr, $key: tt) => {
|
||||||
if ($rv.$key).is_some() {
|
if ($rv.$key).is_some() {
|
||||||
$lv.$key = $rv.$key;
|
$lv.$key = $rv.$key;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Profiles {
|
impl Profiles {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Profiles::read_file()
|
Profiles::read_file()
|
||||||
}
|
|
||||||
|
|
||||||
/// read the config from the file
|
|
||||||
pub fn read_file() -> Self {
|
|
||||||
let mut profiles = config::read_yaml::<Self>(dirs::profiles_path());
|
|
||||||
|
|
||||||
if profiles.items.is_none() {
|
|
||||||
profiles.items = Some(vec![]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// compatiable with the old old old version
|
/// read the config from the file
|
||||||
profiles.items.as_mut().map(|items| {
|
pub fn read_file() -> Self {
|
||||||
for mut item in items.iter_mut() {
|
let mut profiles = config::read_yaml::<Self>(dirs::profiles_path());
|
||||||
|
|
||||||
|
if profiles.items.is_none() {
|
||||||
|
profiles.items = Some(vec![]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// compatiable with the old old old version
|
||||||
|
profiles.items.as_mut().map(|items| {
|
||||||
|
for mut item in items.iter_mut() {
|
||||||
|
if item.uid.is_none() {
|
||||||
|
item.uid = Some(help::get_uid("d"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
profiles
|
||||||
|
}
|
||||||
|
|
||||||
|
/// save the config to the file
|
||||||
|
pub fn save_file(&self) -> Result<()> {
|
||||||
|
config::save_yaml(
|
||||||
|
dirs::profiles_path(),
|
||||||
|
self,
|
||||||
|
Some("# Profiles Config for Clash Verge\n\n"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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<()> {
|
||||||
|
if self.items.is_none() {
|
||||||
|
self.items = Some(vec![]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let items = self.items.as_ref().unwrap();
|
||||||
|
let some_uid = Some(uid.clone());
|
||||||
|
|
||||||
|
if items.iter().find(|&each| each.uid == some_uid).is_some() {
|
||||||
|
self.current = some_uid;
|
||||||
|
return self.save_file();
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!("invalid uid \"{uid}\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// just change the `chain`
|
||||||
|
pub fn put_chain(&mut self, chain: Option<Vec<String>>) -> Result<()> {
|
||||||
|
self.chain = chain;
|
||||||
|
self.save_file()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// just change the `field`
|
||||||
|
pub fn put_valid(&mut self, valid: Option<Vec<String>>) -> Result<()> {
|
||||||
|
self.valid = valid;
|
||||||
|
self.save_file()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get items ref
|
||||||
|
pub fn get_items(&self) -> Option<&Vec<PrfItem>> {
|
||||||
|
self.items.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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();
|
||||||
|
let some_uid = Some(uid.clone());
|
||||||
|
|
||||||
|
for each in items.iter() {
|
||||||
|
if each.uid == some_uid {
|
||||||
|
return Ok(each);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!("failed to get the profile item \"uid:{uid}\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// append new item
|
||||||
|
/// if the file_data is some
|
||||||
|
/// then should save the data to file
|
||||||
|
pub fn append_item(&mut self, mut item: PrfItem) -> Result<()> {
|
||||||
if item.uid.is_none() {
|
if item.uid.is_none() {
|
||||||
item.uid = Some(help::get_uid("d"));
|
bail!("the uid should not be null");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
profiles
|
// save the file data
|
||||||
}
|
// move the field value after save
|
||||||
|
if let Some(file_data) = item.file_data.take() {
|
||||||
/// save the config to the file
|
if item.file.is_none() {
|
||||||
pub fn save_file(&self) -> Result<()> {
|
bail!("the file should not be null");
|
||||||
config::save_yaml(
|
}
|
||||||
dirs::profiles_path(),
|
|
||||||
self,
|
|
||||||
Some("# Profiles Config for Clash Verge\n\n"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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<()> {
|
|
||||||
if self.items.is_none() {
|
|
||||||
self.items = Some(vec![]);
|
|
||||||
}
|
|
||||||
|
|
||||||
let items = self.items.as_ref().unwrap();
|
|
||||||
let some_uid = Some(uid.clone());
|
|
||||||
|
|
||||||
if items.iter().find(|&each| each.uid == some_uid).is_some() {
|
|
||||||
self.current = some_uid;
|
|
||||||
return self.save_file();
|
|
||||||
}
|
|
||||||
|
|
||||||
bail!("invalid uid \"{uid}\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// just change the `chain`
|
|
||||||
pub fn put_chain(&mut self, chain: Option<Vec<String>>) -> Result<()> {
|
|
||||||
self.chain = chain;
|
|
||||||
self.save_file()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// just change the `field`
|
|
||||||
pub fn put_valid(&mut self, valid: Option<Vec<String>>) -> Result<()> {
|
|
||||||
self.valid = valid;
|
|
||||||
self.save_file()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// get items ref
|
|
||||||
pub fn get_items(&self) -> Option<&Vec<PrfItem>> {
|
|
||||||
self.items.as_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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();
|
|
||||||
let some_uid = Some(uid.clone());
|
|
||||||
|
|
||||||
for each in items.iter() {
|
|
||||||
if each.uid == some_uid {
|
|
||||||
return Ok(each);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bail!("failed to get the profile item \"uid:{uid}\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// append new item
|
|
||||||
/// if the file_data is some
|
|
||||||
/// then should save the data to file
|
|
||||||
pub fn append_item(&mut self, mut item: PrfItem) -> Result<()> {
|
|
||||||
if item.uid.is_none() {
|
|
||||||
bail!("the uid should not be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
// save the file data
|
|
||||||
// move the field value after save
|
|
||||||
if let Some(file_data) = item.file_data.take() {
|
|
||||||
if item.file.is_none() {
|
|
||||||
bail!("the file should not be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
let file = item.file.clone().unwrap();
|
|
||||||
let path = dirs::app_profiles_dir().join(&file);
|
|
||||||
|
|
||||||
fs::File::create(path)
|
|
||||||
.context(format!("failed to create file \"{}\"", file))?
|
|
||||||
.write(file_data.as_bytes())
|
|
||||||
.context(format!("failed to write to file \"{}\"", file))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.items.is_none() {
|
|
||||||
self.items = Some(vec![]);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.items.as_mut().map(|items| items.push(item));
|
|
||||||
self.save_file()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// update the item value
|
|
||||||
pub fn patch_item(&mut self, uid: String, item: PrfItem) -> Result<()> {
|
|
||||||
let mut items = self.items.take().unwrap_or(vec![]);
|
|
||||||
|
|
||||||
for mut each in items.iter_mut() {
|
|
||||||
if each.uid == Some(uid.clone()) {
|
|
||||||
patch!(each, item, itype);
|
|
||||||
patch!(each, item, name);
|
|
||||||
patch!(each, item, desc);
|
|
||||||
patch!(each, item, file);
|
|
||||||
patch!(each, item, url);
|
|
||||||
patch!(each, item, selected);
|
|
||||||
patch!(each, item, extra);
|
|
||||||
patch!(each, item, updated);
|
|
||||||
patch!(each, item, option);
|
|
||||||
|
|
||||||
self.items = Some(items);
|
|
||||||
return self.save_file();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.items = Some(items);
|
|
||||||
bail!("failed to find the profile item \"uid:{uid}\"")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// be used to update the remote item
|
|
||||||
/// only patch `updated` `extra` `file_data`
|
|
||||||
pub fn update_item(&mut self, uid: String, mut item: PrfItem) -> Result<()> {
|
|
||||||
if self.items.is_none() {
|
|
||||||
self.items = Some(vec![]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// find the item
|
|
||||||
let _ = self.get_item(&uid)?;
|
|
||||||
|
|
||||||
if let Some(items) = self.items.as_mut() {
|
|
||||||
let some_uid = Some(uid.clone());
|
|
||||||
|
|
||||||
for mut each in items.iter_mut() {
|
|
||||||
if each.uid == some_uid {
|
|
||||||
each.extra = item.extra;
|
|
||||||
each.updated = item.updated;
|
|
||||||
|
|
||||||
// save the file data
|
|
||||||
// move the field value after save
|
|
||||||
if let Some(file_data) = item.file_data.take() {
|
|
||||||
let file = each.file.take();
|
|
||||||
let file = file.unwrap_or(item.file.take().unwrap_or(format!("{}.yaml", &uid)));
|
|
||||||
|
|
||||||
// the file must exists
|
|
||||||
each.file = Some(file.clone());
|
|
||||||
|
|
||||||
|
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)
|
fs::File::create(path)
|
||||||
.context(format!("failed to create file \"{}\"", file))?
|
.context(format!("failed to create file \"{}\"", file))?
|
||||||
.write(file_data.as_bytes())
|
.write(file_data.as_bytes())
|
||||||
.context(format!("failed to write to file \"{}\"", file))?;
|
.context(format!("failed to write to file \"{}\"", file))?;
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.save_file()
|
if self.items.is_none() {
|
||||||
}
|
self.items = Some(vec![]);
|
||||||
|
|
||||||
/// delete item
|
|
||||||
/// if delete the current then return true
|
|
||||||
pub fn delete_item(&mut self, uid: String) -> Result<bool> {
|
|
||||||
let current = self.current.as_ref().unwrap_or(&uid);
|
|
||||||
let current = current.clone();
|
|
||||||
|
|
||||||
let mut items = self.items.take().unwrap_or(vec![]);
|
|
||||||
let mut index = None;
|
|
||||||
|
|
||||||
// get the index
|
|
||||||
for i in 0..items.len() {
|
|
||||||
if items[i].uid == Some(uid.clone()) {
|
|
||||||
index = Some(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(index) = index {
|
|
||||||
items.remove(index).file.map(|file| {
|
|
||||||
let path = dirs::app_profiles_dir().join(file);
|
|
||||||
if path.exists() {
|
|
||||||
let _ = fs::remove_file(path);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
self.items.as_mut().map(|items| items.push(item));
|
||||||
|
self.save_file()
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete the original uid
|
/// update the item value
|
||||||
if current == uid {
|
pub fn patch_item(&mut self, uid: String, item: PrfItem) -> Result<()> {
|
||||||
self.current = match items.len() > 0 {
|
let mut items = self.items.take().unwrap_or(vec![]);
|
||||||
true => items[0].uid.clone(),
|
|
||||||
false => None,
|
for mut each in items.iter_mut() {
|
||||||
};
|
if each.uid == Some(uid.clone()) {
|
||||||
|
patch!(each, item, itype);
|
||||||
|
patch!(each, item, name);
|
||||||
|
patch!(each, item, desc);
|
||||||
|
patch!(each, item, file);
|
||||||
|
patch!(each, item, url);
|
||||||
|
patch!(each, item, selected);
|
||||||
|
patch!(each, item, extra);
|
||||||
|
patch!(each, item, updated);
|
||||||
|
patch!(each, item, option);
|
||||||
|
|
||||||
|
self.items = Some(items);
|
||||||
|
return self.save_file();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.items = Some(items);
|
||||||
|
bail!("failed to find the profile item \"uid:{uid}\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
self.items = Some(items);
|
/// be used to update the remote item
|
||||||
self.save_file()?;
|
/// only patch `updated` `extra` `file_data`
|
||||||
Ok(current == uid)
|
pub fn update_item(&mut self, uid: String, mut item: PrfItem) -> Result<()> {
|
||||||
}
|
if self.items.is_none() {
|
||||||
|
self.items = Some(vec![]);
|
||||||
|
}
|
||||||
|
|
||||||
/// generate the current Mapping data
|
// find the item
|
||||||
fn gen_current(&self) -> Result<Mapping> {
|
let _ = self.get_item(&uid)?;
|
||||||
let config = Mapping::new();
|
|
||||||
|
|
||||||
if self.current.is_none() || self.items.is_none() {
|
if let Some(items) = self.items.as_mut() {
|
||||||
return Ok(config);
|
let some_uid = Some(uid.clone());
|
||||||
|
|
||||||
|
for mut each in items.iter_mut() {
|
||||||
|
if each.uid == some_uid {
|
||||||
|
each.extra = item.extra;
|
||||||
|
each.updated = item.updated;
|
||||||
|
|
||||||
|
// save the file data
|
||||||
|
// move the field value after save
|
||||||
|
if let Some(file_data) = item.file_data.take() {
|
||||||
|
let file = each.file.take();
|
||||||
|
let file =
|
||||||
|
file.unwrap_or(item.file.take().unwrap_or(format!("{}.yaml", &uid)));
|
||||||
|
|
||||||
|
// the file must exists
|
||||||
|
each.file = Some(file.clone());
|
||||||
|
|
||||||
|
let path = dirs::app_profiles_dir().join(&file);
|
||||||
|
|
||||||
|
fs::File::create(path)
|
||||||
|
.context(format!("failed to create file \"{}\"", file))?
|
||||||
|
.write(file_data.as_bytes())
|
||||||
|
.context(format!("failed to write to file \"{}\"", file))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.save_file()
|
||||||
}
|
}
|
||||||
|
|
||||||
let current = self.current.clone().unwrap();
|
/// delete item
|
||||||
for item in self.items.as_ref().unwrap().iter() {
|
/// if delete the current then return true
|
||||||
if item.uid == Some(current.clone()) {
|
pub fn delete_item(&mut self, uid: String) -> Result<bool> {
|
||||||
let file_path = match item.file.clone() {
|
let current = self.current.as_ref().unwrap_or(&uid);
|
||||||
Some(file) => dirs::app_profiles_dir().join(file),
|
let current = current.clone();
|
||||||
None => bail!("failed to get the file field"),
|
|
||||||
|
let mut items = self.items.take().unwrap_or(vec![]);
|
||||||
|
let mut index = None;
|
||||||
|
|
||||||
|
// get the index
|
||||||
|
for i in 0..items.len() {
|
||||||
|
if items[i].uid == Some(uid.clone()) {
|
||||||
|
index = Some(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(index) = index {
|
||||||
|
items.remove(index).file.map(|file| {
|
||||||
|
let path = dirs::app_profiles_dir().join(file);
|
||||||
|
if path.exists() {
|
||||||
|
let _ = fs::remove_file(path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete the original uid
|
||||||
|
if current == uid {
|
||||||
|
self.current = match items.len() > 0 {
|
||||||
|
true => items[0].uid.clone(),
|
||||||
|
false => None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
self.items = Some(items);
|
||||||
|
self.save_file()?;
|
||||||
|
Ok(current == uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// generate the current Mapping data
|
||||||
|
fn gen_current(&self) -> Result<Mapping> {
|
||||||
|
let config = Mapping::new();
|
||||||
|
|
||||||
|
if self.current.is_none() || self.items.is_none() {
|
||||||
|
return Ok(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
let current = self.current.clone().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),
|
||||||
|
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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bail!("failed to find current profile \"uid:{current}\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// generate the data for activate clash config
|
||||||
|
pub fn gen_activate(&self) -> Result<PrfActivate> {
|
||||||
|
let current = self.gen_current()?;
|
||||||
|
let chain = match self.chain.as_ref() {
|
||||||
|
Some(chain) => chain
|
||||||
|
.iter()
|
||||||
|
.filter_map(|uid| self.get_item(uid).ok())
|
||||||
|
.filter_map(|item| item.to_enhance())
|
||||||
|
.collect::<Vec<ChainItem>>(),
|
||||||
|
None => vec![],
|
||||||
};
|
};
|
||||||
|
let valid = self.valid.clone().unwrap_or(vec![]);
|
||||||
|
|
||||||
if !file_path.exists() {
|
Ok(PrfActivate {
|
||||||
bail!("failed to read the file \"{}\"", file_path.display());
|
current,
|
||||||
}
|
chain,
|
||||||
|
valid,
|
||||||
return Ok(config::read_merge_mapping(file_path.clone()));
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
bail!("failed to find current profile \"uid:{current}\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// generate the data for activate clash config
|
|
||||||
pub fn gen_activate(&self) -> Result<PrfActivate> {
|
|
||||||
let current = self.gen_current()?;
|
|
||||||
let chain = match self.chain.as_ref() {
|
|
||||||
Some(chain) => chain
|
|
||||||
.iter()
|
|
||||||
.filter_map(|uid| self.get_item(uid).ok())
|
|
||||||
.filter_map(|item| item.to_enhance())
|
|
||||||
.collect::<Vec<ChainItem>>(),
|
|
||||||
None => vec![],
|
|
||||||
};
|
|
||||||
let valid = self.valid.clone().unwrap_or(vec![]);
|
|
||||||
|
|
||||||
Ok(PrfActivate {
|
|
||||||
current,
|
|
||||||
chain,
|
|
||||||
valid,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
pub struct PrfActivate {
|
pub struct PrfActivate {
|
||||||
pub current: Mapping,
|
pub current: Mapping,
|
||||||
pub chain: Vec<ChainItem>,
|
pub chain: Vec<ChainItem>,
|
||||||
pub valid: Vec<String>,
|
pub valid: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct RuntimeResult {
|
pub struct RuntimeResult {
|
||||||
pub config: Option<Mapping>,
|
pub config: Option<Mapping>,
|
||||||
pub config_yaml: Option<String>,
|
pub config_yaml: Option<String>,
|
||||||
// 记录在配置中(包括merge和script生成的)出现过的keys
|
// 记录在配置中(包括merge和script生成的)出现过的keys
|
||||||
// 这些keys不一定都生效
|
// 这些keys不一定都生效
|
||||||
pub exists_keys: Vec<String>,
|
pub exists_keys: Vec<String>,
|
||||||
pub chain_logs: HashMap<String, Vec<(String, String)>>,
|
pub chain_logs: HashMap<String, Vec<(String, String)>>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,132 +5,132 @@ use serde::{Deserialize, Serialize};
|
||||||
/// ### `verge.yaml` schema
|
/// ### `verge.yaml` schema
|
||||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct Verge {
|
pub struct Verge {
|
||||||
/// app listening port
|
/// app listening port
|
||||||
/// for app singleton
|
/// for app singleton
|
||||||
pub app_singleton_port: Option<u16>,
|
pub app_singleton_port: Option<u16>,
|
||||||
|
|
||||||
// i18n
|
// i18n
|
||||||
pub language: Option<String>,
|
pub language: Option<String>,
|
||||||
|
|
||||||
/// `light` or `dark` or `system`
|
/// `light` or `dark` or `system`
|
||||||
pub theme_mode: Option<String>,
|
pub theme_mode: Option<String>,
|
||||||
|
|
||||||
/// enable blur mode
|
/// enable blur mode
|
||||||
/// maybe be able to set the alpha
|
/// maybe be able to set the alpha
|
||||||
pub theme_blur: Option<bool>,
|
pub theme_blur: Option<bool>,
|
||||||
|
|
||||||
/// enable traffic graph default is true
|
/// enable traffic graph default is true
|
||||||
pub traffic_graph: Option<bool>,
|
pub traffic_graph: Option<bool>,
|
||||||
|
|
||||||
/// clash tun mode
|
/// clash tun mode
|
||||||
pub enable_tun_mode: Option<bool>,
|
pub enable_tun_mode: Option<bool>,
|
||||||
|
|
||||||
/// windows service mode
|
/// windows service mode
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub enable_service_mode: Option<bool>,
|
pub enable_service_mode: Option<bool>,
|
||||||
|
|
||||||
/// can the app auto startup
|
/// can the app auto startup
|
||||||
pub enable_auto_launch: Option<bool>,
|
pub enable_auto_launch: Option<bool>,
|
||||||
|
|
||||||
/// not show the window on launch
|
/// not show the window on launch
|
||||||
pub enable_silent_start: Option<bool>,
|
pub enable_silent_start: Option<bool>,
|
||||||
|
|
||||||
/// set system proxy
|
/// set system proxy
|
||||||
pub enable_system_proxy: Option<bool>,
|
pub enable_system_proxy: Option<bool>,
|
||||||
|
|
||||||
/// enable proxy guard
|
/// enable proxy guard
|
||||||
pub enable_proxy_guard: Option<bool>,
|
pub enable_proxy_guard: Option<bool>,
|
||||||
|
|
||||||
/// set system proxy bypass
|
/// set system proxy bypass
|
||||||
pub system_proxy_bypass: Option<String>,
|
pub system_proxy_bypass: Option<String>,
|
||||||
|
|
||||||
/// proxy guard duration
|
/// proxy guard duration
|
||||||
pub proxy_guard_duration: Option<u64>,
|
pub proxy_guard_duration: Option<u64>,
|
||||||
|
|
||||||
/// theme setting
|
/// theme setting
|
||||||
pub theme_setting: Option<VergeTheme>,
|
pub theme_setting: Option<VergeTheme>,
|
||||||
|
|
||||||
/// web ui list
|
/// web ui list
|
||||||
pub web_ui_list: Option<Vec<String>>,
|
pub web_ui_list: Option<Vec<String>>,
|
||||||
|
|
||||||
/// clash core path
|
/// clash core path
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub clash_core: Option<String>,
|
pub clash_core: Option<String>,
|
||||||
|
|
||||||
/// hotkey map
|
/// hotkey map
|
||||||
/// format: {func},{key}
|
/// format: {func},{key}
|
||||||
pub hotkeys: Option<Vec<String>>,
|
pub hotkeys: Option<Vec<String>>,
|
||||||
|
|
||||||
/// 切换代理时自动关闭连接
|
/// 切换代理时自动关闭连接
|
||||||
pub auto_close_connection: Option<bool>,
|
pub auto_close_connection: Option<bool>,
|
||||||
|
|
||||||
/// 默认的延迟测试连接
|
/// 默认的延迟测试连接
|
||||||
pub default_latency_test: Option<String>,
|
pub default_latency_test: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct VergeTheme {
|
pub struct VergeTheme {
|
||||||
pub primary_color: Option<String>,
|
pub primary_color: Option<String>,
|
||||||
pub secondary_color: Option<String>,
|
pub secondary_color: Option<String>,
|
||||||
pub primary_text: Option<String>,
|
pub primary_text: Option<String>,
|
||||||
pub secondary_text: Option<String>,
|
pub secondary_text: Option<String>,
|
||||||
|
|
||||||
pub info_color: Option<String>,
|
pub info_color: Option<String>,
|
||||||
pub error_color: Option<String>,
|
pub error_color: Option<String>,
|
||||||
pub warning_color: Option<String>,
|
pub warning_color: Option<String>,
|
||||||
pub success_color: Option<String>,
|
pub success_color: Option<String>,
|
||||||
|
|
||||||
pub font_family: Option<String>,
|
pub font_family: Option<String>,
|
||||||
pub css_injection: Option<String>,
|
pub css_injection: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Verge {
|
impl Verge {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
config::read_yaml::<Verge>(dirs::verge_path())
|
config::read_yaml::<Verge>(dirs::verge_path())
|
||||||
}
|
|
||||||
|
|
||||||
/// Save Verge App Config
|
|
||||||
pub fn save_file(&self) -> Result<()> {
|
|
||||||
config::save_yaml(
|
|
||||||
dirs::verge_path(),
|
|
||||||
self,
|
|
||||||
Some("# The Config for Clash Verge App\n\n"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// patch verge config
|
|
||||||
/// only save to file
|
|
||||||
pub fn patch_config(&mut self, patch: Verge) -> Result<()> {
|
|
||||||
macro_rules! patch {
|
|
||||||
($key: tt) => {
|
|
||||||
if patch.$key.is_some() {
|
|
||||||
self.$key = patch.$key;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
patch!(language);
|
/// Save Verge App Config
|
||||||
patch!(theme_mode);
|
pub fn save_file(&self) -> Result<()> {
|
||||||
patch!(theme_blur);
|
config::save_yaml(
|
||||||
patch!(traffic_graph);
|
dirs::verge_path(),
|
||||||
|
self,
|
||||||
|
Some("# The Config for Clash Verge App\n\n"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
patch!(enable_tun_mode);
|
/// patch verge config
|
||||||
patch!(enable_service_mode);
|
/// only save to file
|
||||||
patch!(enable_auto_launch);
|
pub fn patch_config(&mut self, patch: Verge) -> Result<()> {
|
||||||
patch!(enable_silent_start);
|
macro_rules! patch {
|
||||||
patch!(enable_system_proxy);
|
($key: tt) => {
|
||||||
patch!(enable_proxy_guard);
|
if patch.$key.is_some() {
|
||||||
patch!(system_proxy_bypass);
|
self.$key = patch.$key;
|
||||||
patch!(proxy_guard_duration);
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
patch!(theme_setting);
|
patch!(language);
|
||||||
patch!(web_ui_list);
|
patch!(theme_mode);
|
||||||
patch!(clash_core);
|
patch!(theme_blur);
|
||||||
patch!(hotkeys);
|
patch!(traffic_graph);
|
||||||
|
|
||||||
patch!(auto_close_connection);
|
patch!(enable_tun_mode);
|
||||||
patch!(default_latency_test);
|
patch!(enable_service_mode);
|
||||||
|
patch!(enable_auto_launch);
|
||||||
|
patch!(enable_silent_start);
|
||||||
|
patch!(enable_system_proxy);
|
||||||
|
patch!(enable_proxy_guard);
|
||||||
|
patch!(system_proxy_bypass);
|
||||||
|
patch!(proxy_guard_duration);
|
||||||
|
|
||||||
self.save_file()
|
patch!(theme_setting);
|
||||||
}
|
patch!(web_ui_list);
|
||||||
|
patch!(clash_core);
|
||||||
|
patch!(hotkeys);
|
||||||
|
|
||||||
|
patch!(auto_close_connection);
|
||||||
|
patch!(default_latency_test);
|
||||||
|
|
||||||
|
self.save_file()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,99 +4,99 @@ use crate::log_if_err;
|
||||||
|
|
||||||
// 重启clash
|
// 重启clash
|
||||||
pub fn restart_clash_core() {
|
pub fn restart_clash_core() {
|
||||||
let core = Core::global();
|
let core = Core::global();
|
||||||
let mut service = core.service.lock();
|
let mut service = core.service.lock();
|
||||||
log_if_err!(service.restart());
|
log_if_err!(service.restart());
|
||||||
drop(service);
|
drop(service);
|
||||||
log_if_err!(core.activate());
|
log_if_err!(core.activate());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 切换模式
|
// 切换模式
|
||||||
pub fn change_clash_mode(mode: &str) {
|
pub fn change_clash_mode(mode: &str) {
|
||||||
let core = Core::global();
|
let core = Core::global();
|
||||||
log_if_err!(core.update_mode(mode));
|
log_if_err!(core.update_mode(mode));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 切换系统代理
|
// 切换系统代理
|
||||||
pub fn toggle_system_proxy() {
|
pub fn toggle_system_proxy() {
|
||||||
let core = Core::global();
|
let core = Core::global();
|
||||||
let data = Data::global();
|
let data = Data::global();
|
||||||
|
|
||||||
let verge = data.verge.lock();
|
let verge = data.verge.lock();
|
||||||
let enable = !verge.enable_system_proxy.clone().unwrap_or(false);
|
let enable = !verge.enable_system_proxy.clone().unwrap_or(false);
|
||||||
drop(verge);
|
drop(verge);
|
||||||
|
|
||||||
log_if_err!(core.patch_verge(Verge {
|
log_if_err!(core.patch_verge(Verge {
|
||||||
enable_system_proxy: Some(enable),
|
enable_system_proxy: Some(enable),
|
||||||
..Verge::default()
|
..Verge::default()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let handle = core.handle.lock();
|
let handle = core.handle.lock();
|
||||||
let _ = handle.refresh_verge();
|
let _ = handle.refresh_verge();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 打开系统代理
|
// 打开系统代理
|
||||||
pub fn enable_system_proxy() {
|
pub fn enable_system_proxy() {
|
||||||
let core = Core::global();
|
let core = Core::global();
|
||||||
log_if_err!(core.patch_verge(Verge {
|
log_if_err!(core.patch_verge(Verge {
|
||||||
enable_system_proxy: Some(true),
|
enable_system_proxy: Some(true),
|
||||||
..Verge::default()
|
..Verge::default()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let handle = core.handle.lock();
|
let handle = core.handle.lock();
|
||||||
let _ = handle.refresh_verge();
|
let _ = handle.refresh_verge();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关闭系统代理
|
// 关闭系统代理
|
||||||
pub fn disable_system_proxy() {
|
pub fn disable_system_proxy() {
|
||||||
let core = Core::global();
|
let core = Core::global();
|
||||||
log_if_err!(core.patch_verge(Verge {
|
log_if_err!(core.patch_verge(Verge {
|
||||||
enable_system_proxy: Some(false),
|
enable_system_proxy: Some(false),
|
||||||
..Verge::default()
|
..Verge::default()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let handle = core.handle.lock();
|
let handle = core.handle.lock();
|
||||||
let _ = handle.refresh_verge();
|
let _ = handle.refresh_verge();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 切换tun模式
|
// 切换tun模式
|
||||||
pub fn toggle_tun_mode() {
|
pub fn toggle_tun_mode() {
|
||||||
let core = Core::global();
|
let core = Core::global();
|
||||||
let data = Data::global();
|
let data = Data::global();
|
||||||
|
|
||||||
let verge = data.verge.lock();
|
let verge = data.verge.lock();
|
||||||
let enable = !verge.enable_tun_mode.clone().unwrap_or(false);
|
let enable = !verge.enable_tun_mode.clone().unwrap_or(false);
|
||||||
drop(verge);
|
drop(verge);
|
||||||
|
|
||||||
log_if_err!(core.patch_verge(Verge {
|
log_if_err!(core.patch_verge(Verge {
|
||||||
enable_tun_mode: Some(enable),
|
enable_tun_mode: Some(enable),
|
||||||
..Verge::default()
|
..Verge::default()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let handle = core.handle.lock();
|
let handle = core.handle.lock();
|
||||||
let _ = handle.refresh_verge();
|
let _ = handle.refresh_verge();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 打开tun模式
|
// 打开tun模式
|
||||||
pub fn enable_tun_mode() {
|
pub fn enable_tun_mode() {
|
||||||
let core = Core::global();
|
let core = Core::global();
|
||||||
log_if_err!(core.patch_verge(Verge {
|
log_if_err!(core.patch_verge(Verge {
|
||||||
enable_tun_mode: Some(true),
|
enable_tun_mode: Some(true),
|
||||||
..Verge::default()
|
..Verge::default()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let handle = core.handle.lock();
|
let handle = core.handle.lock();
|
||||||
let _ = handle.refresh_verge();
|
let _ = handle.refresh_verge();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关闭tun模式
|
// 关闭tun模式
|
||||||
pub fn disable_tun_mode() {
|
pub fn disable_tun_mode() {
|
||||||
let core = Core::global();
|
let core = Core::global();
|
||||||
log_if_err!(core.patch_verge(Verge {
|
log_if_err!(core.patch_verge(Verge {
|
||||||
enable_tun_mode: Some(false),
|
enable_tun_mode: Some(false),
|
||||||
..Verge::default()
|
..Verge::default()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let handle = core.handle.lock();
|
let handle = core.handle.lock();
|
||||||
let _ = handle.refresh_verge();
|
let _ = handle.refresh_verge();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#![cfg_attr(
|
#![cfg_attr(
|
||||||
all(not(debug_assertions), target_os = "windows"),
|
all(not(debug_assertions), target_os = "windows"),
|
||||||
windows_subsystem = "windows"
|
windows_subsystem = "windows"
|
||||||
)]
|
)]
|
||||||
|
|
||||||
mod cmds;
|
mod cmds;
|
||||||
|
@ -14,129 +14,129 @@ use crate::utils::{init, resolve, server};
|
||||||
use tauri::{api, Manager, SystemTray};
|
use tauri::{api, Manager, SystemTray};
|
||||||
|
|
||||||
fn main() -> std::io::Result<()> {
|
fn main() -> std::io::Result<()> {
|
||||||
// 单例检测
|
// 单例检测
|
||||||
if server::check_singleton().is_err() {
|
if server::check_singleton().is_err() {
|
||||||
println!("app exists");
|
println!("app exists");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
unsafe {
|
|
||||||
use crate::utils::dirs;
|
|
||||||
dirs::init_portable_flag();
|
|
||||||
}
|
|
||||||
|
|
||||||
crate::log_if_err!(init::init_config());
|
|
||||||
|
|
||||||
#[allow(unused_mut)]
|
|
||||||
let mut builder = tauri::Builder::default()
|
|
||||||
.setup(|app| Ok(resolve::resolve_setup(app)))
|
|
||||||
.system_tray(SystemTray::new())
|
|
||||||
.on_system_tray_event(core::tray::Tray::on_system_tray_event)
|
|
||||||
.invoke_handler(tauri::generate_handler![
|
|
||||||
// common
|
|
||||||
cmds::get_sys_proxy,
|
|
||||||
cmds::open_app_dir,
|
|
||||||
cmds::open_logs_dir,
|
|
||||||
cmds::open_web_url,
|
|
||||||
cmds::kill_sidecar,
|
|
||||||
cmds::restart_sidecar,
|
|
||||||
// clash
|
|
||||||
cmds::get_clash_info,
|
|
||||||
cmds::get_clash_logs,
|
|
||||||
cmds::patch_clash_config,
|
|
||||||
cmds::change_clash_core,
|
|
||||||
cmds::get_runtime_config,
|
|
||||||
cmds::get_runtime_yaml,
|
|
||||||
cmds::get_runtime_exists,
|
|
||||||
cmds::get_runtime_logs,
|
|
||||||
// verge
|
|
||||||
cmds::get_verge_config,
|
|
||||||
cmds::patch_verge_config,
|
|
||||||
cmds::update_hotkeys,
|
|
||||||
// profile
|
|
||||||
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,
|
|
||||||
]);
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
{
|
|
||||||
use tauri::{Menu, MenuItem, Submenu};
|
|
||||||
|
|
||||||
builder = builder.menu(
|
|
||||||
Menu::new().add_submenu(Submenu::new(
|
|
||||||
"Edit",
|
|
||||||
Menu::new()
|
|
||||||
.add_native_item(MenuItem::Undo)
|
|
||||||
.add_native_item(MenuItem::Redo)
|
|
||||||
.add_native_item(MenuItem::Copy)
|
|
||||||
.add_native_item(MenuItem::Paste)
|
|
||||||
.add_native_item(MenuItem::Cut)
|
|
||||||
.add_native_item(MenuItem::SelectAll)
|
|
||||||
.add_native_item(MenuItem::CloseWindow)
|
|
||||||
.add_native_item(MenuItem::Quit)
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused_mut)]
|
|
||||||
let mut 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();
|
|
||||||
}
|
}
|
||||||
tauri::RunEvent::Exit => {
|
|
||||||
resolve::resolve_reset();
|
#[cfg(target_os = "windows")]
|
||||||
api::process::kill_children();
|
unsafe {
|
||||||
app_handle.exit(0);
|
use crate::utils::dirs;
|
||||||
|
dirs::init_portable_flag();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crate::log_if_err!(init::init_config());
|
||||||
|
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut builder = tauri::Builder::default()
|
||||||
|
.setup(|app| Ok(resolve::resolve_setup(app)))
|
||||||
|
.system_tray(SystemTray::new())
|
||||||
|
.on_system_tray_event(core::tray::Tray::on_system_tray_event)
|
||||||
|
.invoke_handler(tauri::generate_handler![
|
||||||
|
// common
|
||||||
|
cmds::get_sys_proxy,
|
||||||
|
cmds::open_app_dir,
|
||||||
|
cmds::open_logs_dir,
|
||||||
|
cmds::open_web_url,
|
||||||
|
cmds::kill_sidecar,
|
||||||
|
cmds::restart_sidecar,
|
||||||
|
// clash
|
||||||
|
cmds::get_clash_info,
|
||||||
|
cmds::get_clash_logs,
|
||||||
|
cmds::patch_clash_config,
|
||||||
|
cmds::change_clash_core,
|
||||||
|
cmds::get_runtime_config,
|
||||||
|
cmds::get_runtime_yaml,
|
||||||
|
cmds::get_runtime_exists,
|
||||||
|
cmds::get_runtime_logs,
|
||||||
|
// verge
|
||||||
|
cmds::get_verge_config,
|
||||||
|
cmds::patch_verge_config,
|
||||||
|
cmds::update_hotkeys,
|
||||||
|
// profile
|
||||||
|
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,
|
||||||
|
]);
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
tauri::RunEvent::WindowEvent { label, event, .. } => {
|
{
|
||||||
if label == "main" {
|
use tauri::{Menu, MenuItem, Submenu};
|
||||||
match event {
|
|
||||||
tauri::WindowEvent::CloseRequested { api, .. } => {
|
|
||||||
api.prevent_close();
|
|
||||||
app_handle.get_window("main").map(|win| {
|
|
||||||
let _ = win.hide();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
builder = builder.menu(
|
||||||
|
Menu::new().add_submenu(Submenu::new(
|
||||||
|
"Edit",
|
||||||
|
Menu::new()
|
||||||
|
.add_native_item(MenuItem::Undo)
|
||||||
|
.add_native_item(MenuItem::Redo)
|
||||||
|
.add_native_item(MenuItem::Copy)
|
||||||
|
.add_native_item(MenuItem::Paste)
|
||||||
|
.add_native_item(MenuItem::Cut)
|
||||||
|
.add_native_item(MenuItem::SelectAll)
|
||||||
|
.add_native_item(MenuItem::CloseWindow)
|
||||||
|
.add_native_item(MenuItem::Quit),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut 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();
|
||||||
|
}
|
||||||
|
tauri::RunEvent::Exit => {
|
||||||
|
resolve::resolve_reset();
|
||||||
|
api::process::kill_children();
|
||||||
|
app_handle.exit(0);
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
tauri::RunEvent::WindowEvent { label, event, .. } => {
|
||||||
|
if label == "main" {
|
||||||
|
match event {
|
||||||
|
tauri::WindowEvent::CloseRequested { api, .. } => {
|
||||||
|
api.prevent_close();
|
||||||
|
app_handle.get_window("main").map(|win| {
|
||||||
|
let _ = win.hide();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,55 +5,55 @@ use std::{fs, path::PathBuf};
|
||||||
|
|
||||||
/// read data from yaml as struct T
|
/// read data from yaml as struct T
|
||||||
pub fn read_yaml<T: DeserializeOwned + Default>(path: PathBuf) -> T {
|
pub fn read_yaml<T: DeserializeOwned + Default>(path: PathBuf) -> T {
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
log::error!(target: "app", "file not found \"{}\"", path.display());
|
log::error!(target: "app", "file not found \"{}\"", path.display());
|
||||||
return T::default();
|
return T::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
let yaml_str = fs::read_to_string(&path).unwrap_or("".into());
|
let yaml_str = fs::read_to_string(&path).unwrap_or("".into());
|
||||||
|
|
||||||
match serde_yaml::from_str::<T>(&yaml_str) {
|
match serde_yaml::from_str::<T>(&yaml_str) {
|
||||||
Ok(val) => val,
|
Ok(val) => val,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
log::error!(target: "app", "failed to read yaml file \"{}\"", path.display());
|
log::error!(target: "app", "failed to read yaml file \"{}\"", path.display());
|
||||||
T::default()
|
T::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// read mapping from yaml fix #165
|
/// read mapping from yaml fix #165
|
||||||
pub fn read_merge_mapping(path: PathBuf) -> Mapping {
|
pub fn read_merge_mapping(path: PathBuf) -> Mapping {
|
||||||
let map = Mapping::new();
|
let map = Mapping::new();
|
||||||
|
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
log::error!(target: "app", "file not found \"{}\"", path.display());
|
log::error!(target: "app", "file not found \"{}\"", path.display());
|
||||||
return map;
|
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());
|
let yaml_str = fs::read_to_string(&path).unwrap_or("".into());
|
||||||
map
|
|
||||||
|
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
|
/// save the data to the file
|
||||||
/// can set `prefix` string to add some comments
|
/// can set `prefix` string to add some comments
|
||||||
pub fn save_yaml<T: Serialize>(path: PathBuf, data: &T, prefix: Option<&str>) -> Result<()> {
|
pub fn save_yaml<T: Serialize>(path: PathBuf, data: &T, prefix: Option<&str>) -> Result<()> {
|
||||||
let data_str = serde_yaml::to_string(data)?;
|
let data_str = serde_yaml::to_string(data)?;
|
||||||
|
|
||||||
let yaml_str = match prefix {
|
let yaml_str = match prefix {
|
||||||
Some(prefix) => format!("{prefix}{data_str}"),
|
Some(prefix) => format!("{prefix}{data_str}"),
|
||||||
None => data_str,
|
None => data_str,
|
||||||
};
|
};
|
||||||
|
|
||||||
let path_str = path.as_os_str().to_string_lossy().to_string();
|
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}\""))
|
fs::write(path, yaml_str.as_bytes()).context(format!("failed to save file \"{path_str}\""))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use std::env::temp_dir;
|
use std::env::temp_dir;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tauri::{
|
use tauri::{
|
||||||
api::path::{home_dir, resource_dir},
|
api::path::{home_dir, resource_dir},
|
||||||
Env, PackageInfo,
|
Env, PackageInfo,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "verge-dev"))]
|
#[cfg(not(feature = "verge-dev"))]
|
||||||
|
@ -26,89 +26,89 @@ pub static mut APP_VERSION: &str = "v1.1.1";
|
||||||
/// initialize portable flag
|
/// initialize portable flag
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub unsafe fn init_portable_flag() {
|
pub unsafe fn init_portable_flag() {
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
use tauri::utils::platform::current_exe;
|
use tauri::utils::platform::current_exe;
|
||||||
|
|
||||||
let exe = current_exe().unwrap();
|
let exe = current_exe().unwrap();
|
||||||
let dir = exe.parent().unwrap();
|
let dir = exe.parent().unwrap();
|
||||||
let dir = PathBuf::from(dir).join(".config/PORTABLE");
|
let dir = PathBuf::from(dir).join(".config/PORTABLE");
|
||||||
|
|
||||||
if dir.exists() {
|
if dir.exists() {
|
||||||
PORTABLE_FLAG = true;
|
PORTABLE_FLAG = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get the verge app home dir
|
/// get the verge app home dir
|
||||||
pub fn app_home_dir() -> PathBuf {
|
pub fn app_home_dir() -> PathBuf {
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
unsafe {
|
unsafe {
|
||||||
use tauri::utils::platform::current_exe;
|
use tauri::utils::platform::current_exe;
|
||||||
|
|
||||||
if !PORTABLE_FLAG {
|
if !PORTABLE_FLAG {
|
||||||
home_dir().unwrap().join(".config").join(APP_DIR)
|
home_dir().unwrap().join(".config").join(APP_DIR)
|
||||||
} else {
|
} else {
|
||||||
let app_exe = current_exe().unwrap();
|
let app_exe = current_exe().unwrap();
|
||||||
let app_exe = dunce::canonicalize(app_exe).unwrap();
|
let app_exe = dunce::canonicalize(app_exe).unwrap();
|
||||||
let app_dir = app_exe.parent().unwrap();
|
let app_dir = app_exe.parent().unwrap();
|
||||||
PathBuf::from(app_dir).join(".config").join(APP_DIR)
|
PathBuf::from(app_dir).join(".config").join(APP_DIR)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
home_dir().unwrap().join(".config").join(APP_DIR)
|
home_dir().unwrap().join(".config").join(APP_DIR)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get the resources dir
|
/// get the resources dir
|
||||||
pub fn app_resources_dir(package_info: &PackageInfo) -> PathBuf {
|
pub fn app_resources_dir(package_info: &PackageInfo) -> PathBuf {
|
||||||
let res_dir = resource_dir(package_info, &Env::default())
|
let res_dir = resource_dir(package_info, &Env::default())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.join("resources");
|
.join("resources");
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
RESOURCE_DIR = Some(res_dir.clone());
|
RESOURCE_DIR = Some(res_dir.clone());
|
||||||
|
|
||||||
let ver = package_info.version.to_string();
|
let ver = package_info.version.to_string();
|
||||||
let ver_str = format!("v{ver}");
|
let ver_str = format!("v{ver}");
|
||||||
APP_VERSION = Box::leak(Box::new(ver_str));
|
APP_VERSION = Box::leak(Box::new(ver_str));
|
||||||
}
|
}
|
||||||
|
|
||||||
res_dir
|
res_dir
|
||||||
}
|
}
|
||||||
|
|
||||||
/// profiles dir
|
/// profiles dir
|
||||||
pub fn app_profiles_dir() -> PathBuf {
|
pub fn app_profiles_dir() -> PathBuf {
|
||||||
app_home_dir().join("profiles")
|
app_home_dir().join("profiles")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// logs dir
|
/// logs dir
|
||||||
pub fn app_logs_dir() -> PathBuf {
|
pub fn app_logs_dir() -> PathBuf {
|
||||||
app_home_dir().join("logs")
|
app_home_dir().join("logs")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clash_path() -> PathBuf {
|
pub fn clash_path() -> PathBuf {
|
||||||
app_home_dir().join(CLASH_CONFIG)
|
app_home_dir().join(CLASH_CONFIG)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verge_path() -> PathBuf {
|
pub fn verge_path() -> PathBuf {
|
||||||
app_home_dir().join(VERGE_CONFIG)
|
app_home_dir().join(VERGE_CONFIG)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn profiles_path() -> PathBuf {
|
pub fn profiles_path() -> PathBuf {
|
||||||
app_home_dir().join(PROFILE_YAML)
|
app_home_dir().join(PROFILE_YAML)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn profiles_temp_path() -> PathBuf {
|
pub fn profiles_temp_path() -> PathBuf {
|
||||||
#[cfg(not(feature = "debug-yml"))]
|
#[cfg(not(feature = "debug-yml"))]
|
||||||
return temp_dir().join(PROFILE_TEMP);
|
return temp_dir().join(PROFILE_TEMP);
|
||||||
|
|
||||||
#[cfg(feature = "debug-yml")]
|
#[cfg(feature = "debug-yml")]
|
||||||
return app_home_dir().join(PROFILE_TEMP);
|
return app_home_dir().join(PROFILE_TEMP);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clash_pid_path() -> PathBuf {
|
pub fn clash_pid_path() -> PathBuf {
|
||||||
unsafe { RESOURCE_DIR.clone().unwrap().join("clash.pid") }
|
unsafe { RESOURCE_DIR.clone().unwrap().join("clash.pid") }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
|
@ -116,23 +116,23 @@ static SERVICE_PATH: &str = "clash-verge-service.exe";
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub fn service_path() -> PathBuf {
|
pub fn service_path() -> PathBuf {
|
||||||
unsafe {
|
unsafe {
|
||||||
let res_dir = RESOURCE_DIR.clone().unwrap();
|
let res_dir = RESOURCE_DIR.clone().unwrap();
|
||||||
res_dir.join(SERVICE_PATH)
|
res_dir.join(SERVICE_PATH)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub fn service_log_file() -> PathBuf {
|
pub fn service_log_file() -> PathBuf {
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
|
|
||||||
let log_dir = app_logs_dir().join("service");
|
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%S").to_string();
|
||||||
let log_file = format!("{}.log", local_time);
|
let log_file = format!("{}.log", local_time);
|
||||||
let log_file = log_dir.join(log_file);
|
let log_file = log_dir.join(log_file);
|
||||||
|
|
||||||
std::fs::create_dir_all(&log_dir).unwrap();
|
std::fs::create_dir_all(&log_dir).unwrap();
|
||||||
|
|
||||||
log_file
|
log_file
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,67 +6,67 @@ use std::str::FromStr;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
pub fn get_now() -> usize {
|
pub fn get_now() -> usize {
|
||||||
SystemTime::now()
|
SystemTime::now()
|
||||||
.duration_since(UNIX_EPOCH)
|
.duration_since(UNIX_EPOCH)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_secs() as _
|
.as_secs() as _
|
||||||
}
|
}
|
||||||
|
|
||||||
const ALPHABET: [char; 62] = [
|
const ALPHABET: [char; 62] = [
|
||||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
|
||||||
'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B',
|
'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B',
|
||||||
'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
|
'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
|
||||||
'V', 'W', 'X', 'Y', 'Z',
|
'V', 'W', 'X', 'Y', 'Z',
|
||||||
];
|
];
|
||||||
|
|
||||||
/// generate the uid
|
/// generate the uid
|
||||||
pub fn get_uid(prefix: &str) -> String {
|
pub fn get_uid(prefix: &str) -> String {
|
||||||
let id = nanoid!(11, &ALPHABET);
|
let id = nanoid!(11, &ALPHABET);
|
||||||
format!("{prefix}{id}")
|
format!("{prefix}{id}")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// parse the string
|
/// parse the string
|
||||||
/// xxx=123123; => 123123
|
/// xxx=123123; => 123123
|
||||||
pub fn parse_str<T: FromStr>(target: &str, key: &str) -> Option<T> {
|
pub fn parse_str<T: FromStr>(target: &str, key: &str) -> Option<T> {
|
||||||
target.find(key).and_then(|idx| {
|
target.find(key).and_then(|idx| {
|
||||||
let idx = idx + key.len();
|
let idx = idx + key.len();
|
||||||
let value = &target[idx..];
|
let value = &target[idx..];
|
||||||
|
|
||||||
match value.split(';').nth(0) {
|
match value.split(';').nth(0) {
|
||||||
Some(value) => value.trim().parse(),
|
Some(value) => value.trim().parse(),
|
||||||
None => value.trim().parse(),
|
None => value.trim().parse(),
|
||||||
}
|
}
|
||||||
.ok()
|
.ok()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// open file
|
/// open file
|
||||||
/// use vscode by default
|
/// use vscode by default
|
||||||
pub fn open_file(path: PathBuf) -> Result<()> {
|
pub fn open_file(path: PathBuf) -> Result<()> {
|
||||||
// use vscode first
|
// use vscode first
|
||||||
if let Ok(code) = which::which("code") {
|
if let Ok(code) = which::which("code") {
|
||||||
let mut command = Command::new(&code);
|
let mut command = Command::new(&code);
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
use std::os::windows::process::CommandExt;
|
use std::os::windows::process::CommandExt;
|
||||||
if let Err(err) = command.creation_flags(0x08000000).arg(&path).spawn() {
|
if let Err(err) = command.creation_flags(0x08000000).arg(&path).spawn() {
|
||||||
log::error!(target: "app", "failed to open with VScode `{err}`");
|
log::error!(target: "app", "failed to open with VScode `{err}`");
|
||||||
open::that(path)?;
|
open::that(path)?;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
if let Err(err) = command.arg(&path).spawn() {
|
||||||
|
log::error!(target: "app", "failed to open with VScode `{err}`");
|
||||||
|
open::that(path)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
open::that(path)?;
|
||||||
if let Err(err) = command.arg(&path).spawn() {
|
Ok(())
|
||||||
log::error!(target: "app", "failed to open with VScode `{err}`");
|
|
||||||
open::that(path)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
open::that(path)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
@ -96,27 +96,27 @@ macro_rules! wrap_err {
|
||||||
/// return the string literal error
|
/// return the string literal error
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! ret_err {
|
macro_rules! ret_err {
|
||||||
($str: expr) => {
|
($str: expr) => {
|
||||||
return Err($str.into())
|
return Err($str.into())
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_value() {
|
fn test_parse_value() {
|
||||||
let test_1 = "upload=111; download=2222; total=3333; expire=444";
|
let test_1 = "upload=111; download=2222; total=3333; expire=444";
|
||||||
let test_2 = "attachment; filename=Clash.yaml";
|
let test_2 = "attachment; filename=Clash.yaml";
|
||||||
|
|
||||||
assert_eq!(parse_str::<usize>(test_1, "upload=").unwrap(), 111);
|
assert_eq!(parse_str::<usize>(test_1, "upload=").unwrap(), 111);
|
||||||
assert_eq!(parse_str::<usize>(test_1, "download=").unwrap(), 2222);
|
assert_eq!(parse_str::<usize>(test_1, "download=").unwrap(), 2222);
|
||||||
assert_eq!(parse_str::<usize>(test_1, "total=").unwrap(), 3333);
|
assert_eq!(parse_str::<usize>(test_1, "total=").unwrap(), 3333);
|
||||||
assert_eq!(parse_str::<usize>(test_1, "expire=").unwrap(), 444);
|
assert_eq!(parse_str::<usize>(test_1, "expire=").unwrap(), 444);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_str::<String>(test_2, "filename=").unwrap(),
|
parse_str::<String>(test_2, "filename=").unwrap(),
|
||||||
format!("Clash.yaml")
|
format!("Clash.yaml")
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(parse_str::<usize>(test_1, "aaa="), None);
|
assert_eq!(parse_str::<usize>(test_1, "aaa="), None);
|
||||||
assert_eq!(parse_str::<usize>(test_1, "upload1="), None);
|
assert_eq!(parse_str::<usize>(test_1, "upload1="), None);
|
||||||
assert_eq!(parse_str::<usize>(test_1, "expire1="), None);
|
assert_eq!(parse_str::<usize>(test_1, "expire1="), None);
|
||||||
assert_eq!(parse_str::<usize>(test_2, "attachment="), None);
|
assert_eq!(parse_str::<usize>(test_2, "attachment="), None);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,94 +12,94 @@ use tauri::PackageInfo;
|
||||||
|
|
||||||
/// initialize this instance's log file
|
/// initialize this instance's log file
|
||||||
fn init_log() -> Result<()> {
|
fn init_log() -> Result<()> {
|
||||||
let log_dir = dirs::app_logs_dir();
|
let log_dir = dirs::app_logs_dir();
|
||||||
if !log_dir.exists() {
|
if !log_dir.exists() {
|
||||||
let _ = fs::create_dir_all(&log_dir);
|
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%S").to_string();
|
||||||
let log_file = format!("{}.log", local_time);
|
let log_file = format!("{}.log", local_time);
|
||||||
let log_file = log_dir.join(log_file);
|
let log_file = log_dir.join(log_file);
|
||||||
|
|
||||||
let time_format = "{d(%Y-%m-%d %H:%M:%S)} - {m}{n}";
|
let time_format = "{d(%Y-%m-%d %H:%M:%S)} - {m}{n}";
|
||||||
let stdout = ConsoleAppender::builder()
|
let stdout = ConsoleAppender::builder()
|
||||||
.encoder(Box::new(PatternEncoder::new(time_format)))
|
.encoder(Box::new(PatternEncoder::new(time_format)))
|
||||||
.build();
|
.build();
|
||||||
let tofile = FileAppender::builder()
|
let tofile = FileAppender::builder()
|
||||||
.encoder(Box::new(PatternEncoder::new(time_format)))
|
.encoder(Box::new(PatternEncoder::new(time_format)))
|
||||||
.build(log_file)?;
|
.build(log_file)?;
|
||||||
|
|
||||||
let config = Config::builder()
|
let config = Config::builder()
|
||||||
.appender(Appender::builder().build("stdout", Box::new(stdout)))
|
.appender(Appender::builder().build("stdout", Box::new(stdout)))
|
||||||
.appender(Appender::builder().build("file", Box::new(tofile)))
|
.appender(Appender::builder().build("file", Box::new(tofile)))
|
||||||
.logger(
|
.logger(
|
||||||
Logger::builder()
|
Logger::builder()
|
||||||
.appenders(["file", "stdout"])
|
.appenders(["file", "stdout"])
|
||||||
.additive(false)
|
.additive(false)
|
||||||
.build("app", LevelFilter::Info),
|
.build("app", LevelFilter::Info),
|
||||||
)
|
)
|
||||||
.build(Root::builder().appender("stdout").build(LevelFilter::Info))?;
|
.build(Root::builder().appender("stdout").build(LevelFilter::Info))?;
|
||||||
|
|
||||||
log4rs::init_config(config)?;
|
log4rs::init_config(config)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize all the files from resources
|
/// Initialize all the files from resources
|
||||||
pub fn init_config() -> Result<()> {
|
pub fn init_config() -> Result<()> {
|
||||||
let _ = init_log();
|
let _ = init_log();
|
||||||
|
|
||||||
let app_dir = dirs::app_home_dir();
|
let app_dir = dirs::app_home_dir();
|
||||||
let profiles_dir = dirs::app_profiles_dir();
|
let profiles_dir = dirs::app_profiles_dir();
|
||||||
|
|
||||||
if !app_dir.exists() {
|
if !app_dir.exists() {
|
||||||
let _ = fs::create_dir_all(&app_dir);
|
let _ = fs::create_dir_all(&app_dir);
|
||||||
}
|
}
|
||||||
if !profiles_dir.exists() {
|
if !profiles_dir.exists() {
|
||||||
let _ = fs::create_dir_all(&profiles_dir);
|
let _ = fs::create_dir_all(&profiles_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
// target path
|
// target path
|
||||||
let clash_path = app_dir.join("config.yaml");
|
let clash_path = app_dir.join("config.yaml");
|
||||||
let verge_path = app_dir.join("verge.yaml");
|
let verge_path = app_dir.join("verge.yaml");
|
||||||
let profile_path = app_dir.join("profiles.yaml");
|
let profile_path = app_dir.join("profiles.yaml");
|
||||||
|
|
||||||
if !clash_path.exists() {
|
if !clash_path.exists() {
|
||||||
fs::File::create(clash_path)?.write(tmpl::CLASH_CONFIG)?;
|
fs::File::create(clash_path)?.write(tmpl::CLASH_CONFIG)?;
|
||||||
}
|
}
|
||||||
if !verge_path.exists() {
|
if !verge_path.exists() {
|
||||||
fs::File::create(verge_path)?.write(tmpl::VERGE_CONFIG)?;
|
fs::File::create(verge_path)?.write(tmpl::VERGE_CONFIG)?;
|
||||||
}
|
}
|
||||||
if !profile_path.exists() {
|
if !profile_path.exists() {
|
||||||
fs::File::create(profile_path)?.write(tmpl::PROFILES_CONFIG)?;
|
fs::File::create(profile_path)?.write(tmpl::PROFILES_CONFIG)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// initialize app
|
/// initialize app
|
||||||
pub fn init_resources(package_info: &PackageInfo) {
|
pub fn init_resources(package_info: &PackageInfo) {
|
||||||
// create app dir
|
// create app dir
|
||||||
let app_dir = dirs::app_home_dir();
|
let app_dir = dirs::app_home_dir();
|
||||||
let res_dir = dirs::app_resources_dir(package_info);
|
let res_dir = dirs::app_resources_dir(package_info);
|
||||||
|
|
||||||
if !app_dir.exists() {
|
if !app_dir.exists() {
|
||||||
let _ = fs::create_dir_all(&app_dir);
|
let _ = fs::create_dir_all(&app_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy the resource file
|
// copy the resource file
|
||||||
let mmdb_path = app_dir.join("Country.mmdb");
|
let mmdb_path = app_dir.join("Country.mmdb");
|
||||||
let mmdb_tmpl = res_dir.join("Country.mmdb");
|
let mmdb_tmpl = res_dir.join("Country.mmdb");
|
||||||
if !mmdb_path.exists() && mmdb_tmpl.exists() {
|
if !mmdb_path.exists() && mmdb_tmpl.exists() {
|
||||||
let _ = fs::copy(mmdb_tmpl, mmdb_path);
|
let _ = fs::copy(mmdb_tmpl, mmdb_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy the wintun.dll
|
// copy the wintun.dll
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
let wintun_path = app_dir.join("wintun.dll");
|
let wintun_path = app_dir.join("wintun.dll");
|
||||||
let wintun_tmpl = res_dir.join("wintun.dll");
|
let wintun_tmpl = res_dir.join("wintun.dll");
|
||||||
if !wintun_path.exists() && wintun_tmpl.exists() {
|
if !wintun_path.exists() && wintun_tmpl.exists() {
|
||||||
let _ = fs::copy(wintun_tmpl, wintun_path);
|
let _ = fs::copy(wintun_tmpl, wintun_path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,110 +1,110 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
core::{tray, Core},
|
core::{tray, Core},
|
||||||
data::Data,
|
data::Data,
|
||||||
utils::init,
|
utils::init,
|
||||||
utils::server,
|
utils::server,
|
||||||
};
|
};
|
||||||
use tauri::{App, AppHandle, Manager};
|
use tauri::{App, AppHandle, Manager};
|
||||||
|
|
||||||
/// handle something when start app
|
/// handle something when start app
|
||||||
pub fn resolve_setup(app: &App) {
|
pub fn resolve_setup(app: &App) {
|
||||||
let _ = app
|
let _ = app
|
||||||
.tray_handle()
|
.tray_handle()
|
||||||
.set_menu(tray::Tray::tray_menu(&app.app_handle()));
|
.set_menu(tray::Tray::tray_menu(&app.app_handle()));
|
||||||
|
|
||||||
init::init_resources(app.package_info());
|
init::init_resources(app.package_info());
|
||||||
|
|
||||||
let silent_start = {
|
let silent_start = {
|
||||||
let global = Data::global();
|
let global = Data::global();
|
||||||
let verge = global.verge.lock();
|
let verge = global.verge.lock();
|
||||||
let singleton = verge.app_singleton_port.clone();
|
let singleton = verge.app_singleton_port.clone();
|
||||||
|
|
||||||
// setup a simple http server for singleton
|
// setup a simple http server for singleton
|
||||||
server::embed_server(&app.app_handle(), singleton);
|
server::embed_server(&app.app_handle(), singleton);
|
||||||
|
|
||||||
verge.enable_silent_start.clone().unwrap_or(false)
|
verge.enable_silent_start.clone().unwrap_or(false)
|
||||||
};
|
};
|
||||||
|
|
||||||
// core should be initialized after init_app fix #122
|
// core should be initialized after init_app fix #122
|
||||||
let core = Core::global();
|
let core = Core::global();
|
||||||
core.init(app.app_handle());
|
core.init(app.app_handle());
|
||||||
|
|
||||||
if !silent_start {
|
if !silent_start {
|
||||||
create_window(&app.app_handle());
|
create_window(&app.app_handle());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// reset system proxy
|
/// reset system proxy
|
||||||
pub fn resolve_reset() {
|
pub fn resolve_reset() {
|
||||||
let core = Core::global();
|
let core = Core::global();
|
||||||
let mut sysopt = core.sysopt.lock();
|
let mut sysopt = core.sysopt.lock();
|
||||||
crate::log_if_err!(sysopt.reset_sysproxy());
|
crate::log_if_err!(sysopt.reset_sysproxy());
|
||||||
drop(sysopt);
|
drop(sysopt);
|
||||||
|
|
||||||
let mut service = core.service.lock();
|
let mut service = core.service.lock();
|
||||||
crate::log_if_err!(service.stop());
|
crate::log_if_err!(service.stop());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// create main window
|
/// create main window
|
||||||
pub fn create_window(app_handle: &AppHandle) {
|
pub fn create_window(app_handle: &AppHandle) {
|
||||||
if let Some(window) = app_handle.get_window("main") {
|
if let Some(window) = app_handle.get_window("main") {
|
||||||
let _ = window.unminimize();
|
let _ = window.unminimize();
|
||||||
let _ = window.show();
|
let _ = window.show();
|
||||||
let _ = window.set_focus();
|
let _ = window.set_focus();
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
let builder = tauri::window::WindowBuilder::new(
|
|
||||||
app_handle,
|
|
||||||
"main".to_string(),
|
|
||||||
tauri::WindowUrl::App("index.html".into()),
|
|
||||||
)
|
|
||||||
.title("Clash Verge")
|
|
||||||
.center()
|
|
||||||
.fullscreen(false)
|
|
||||||
.min_inner_size(600.0, 520.0);
|
|
||||||
|
|
||||||
#[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)
|
|
||||||
.build()
|
|
||||||
{
|
|
||||||
Ok(_) => {
|
|
||||||
let app_handle = app_handle.clone();
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Err(err) => log::error!(target: "app", "{err}"),
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
let builder = tauri::window::WindowBuilder::new(
|
||||||
crate::log_if_err!(builder.decorations(true).inner_size(800.0, 642.0).build());
|
app_handle,
|
||||||
|
"main".to_string(),
|
||||||
|
tauri::WindowUrl::App("index.html".into()),
|
||||||
|
)
|
||||||
|
.title("Clash Verge")
|
||||||
|
.center()
|
||||||
|
.fullscreen(false)
|
||||||
|
.min_inner_size(600.0, 520.0);
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "windows")]
|
||||||
crate::log_if_err!(builder
|
{
|
||||||
.decorations(false)
|
use crate::utils::winhelp;
|
||||||
.transparent(true)
|
use std::time::Duration;
|
||||||
.inner_size(800.0, 636.0)
|
use tokio::time::sleep;
|
||||||
.build());
|
use window_shadows::set_shadow;
|
||||||
|
use window_vibrancy::apply_blur;
|
||||||
|
|
||||||
|
match builder
|
||||||
|
.decorations(false)
|
||||||
|
.transparent(true)
|
||||||
|
.inner_size(800.0, 636.0)
|
||||||
|
.build()
|
||||||
|
{
|
||||||
|
Ok(_) => {
|
||||||
|
let app_handle = app_handle.clone();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Err(err) => log::error!(target: "app", "{err}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
crate::log_if_err!(builder.decorations(true).inner_size(800.0, 642.0).build());
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
crate::log_if_err!(builder
|
||||||
|
.decorations(false)
|
||||||
|
.transparent(true)
|
||||||
|
.inner_size(800.0, 636.0)
|
||||||
|
.build());
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,32 +13,32 @@ const SERVER_PORT: u16 = 11233;
|
||||||
|
|
||||||
/// check whether there is already exists
|
/// check whether there is already exists
|
||||||
pub fn check_singleton() -> Result<(), ()> {
|
pub fn check_singleton() -> Result<(), ()> {
|
||||||
let verge = Verge::new();
|
let verge = Verge::new();
|
||||||
let port = verge.app_singleton_port.unwrap_or(SERVER_PORT);
|
let port = verge.app_singleton_port.unwrap_or(SERVER_PORT);
|
||||||
|
|
||||||
if !local_port_available(port) {
|
if !local_port_available(port) {
|
||||||
tauri::async_runtime::block_on(async {
|
tauri::async_runtime::block_on(async {
|
||||||
let url = format!("http://127.0.0.1:{}/commands/visible", port);
|
let url = format!("http://127.0.0.1:{}/commands/visible", port);
|
||||||
reqwest::get(url).await.unwrap();
|
reqwest::get(url).await.unwrap();
|
||||||
Err(())
|
Err(())
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The embed server only be used to implement singleton process
|
/// The embed server only be used to implement singleton process
|
||||||
/// maybe it can be used as pac server later
|
/// maybe it can be used as pac server later
|
||||||
pub fn embed_server(app_handle: &AppHandle, port: Option<u16>) {
|
pub fn embed_server(app_handle: &AppHandle, port: Option<u16>) {
|
||||||
let app_handle = app_handle.clone();
|
let app_handle = app_handle.clone();
|
||||||
let port = port.unwrap_or(SERVER_PORT);
|
let port = port.unwrap_or(SERVER_PORT);
|
||||||
|
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let commands = warp::path!("commands" / "visible").map(move || {
|
let commands = warp::path!("commands" / "visible").map(move || {
|
||||||
resolve::create_window(&app_handle);
|
resolve::create_window(&app_handle);
|
||||||
return format!("ok");
|
return format!("ok");
|
||||||
|
});
|
||||||
|
|
||||||
|
warp::serve(commands).bind(([127, 0, 0, 1], port)).await;
|
||||||
});
|
});
|
||||||
|
|
||||||
warp::serve(commands).bind(([127, 0, 0, 1], port)).await;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,69 +1,69 @@
|
||||||
#![cfg(target_os = "windows")]
|
#![cfg(target_os = "windows")]
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
#![allow(non_camel_case_types)]
|
#![allow(non_camel_case_types)]
|
||||||
|
|
||||||
//!
|
//!
|
||||||
//! From https://github.com/tauri-apps/window-vibrancy/blob/dev/src/windows.rs
|
//! From https://github.com/tauri-apps/window-vibrancy/blob/dev/src/windows.rs
|
||||||
//!
|
//!
|
||||||
|
|
||||||
use windows_sys::Win32::{
|
use windows_sys::Win32::{
|
||||||
Foundation::*,
|
Foundation::*,
|
||||||
System::{LibraryLoader::*, SystemInformation::*},
|
System::{LibraryLoader::*, SystemInformation::*},
|
||||||
};
|
};
|
||||||
|
|
||||||
fn get_function_impl(library: &str, function: &str) -> Option<FARPROC> {
|
fn get_function_impl(library: &str, function: &str) -> Option<FARPROC> {
|
||||||
assert_eq!(library.chars().last(), Some('\0'));
|
assert_eq!(library.chars().last(), Some('\0'));
|
||||||
assert_eq!(function.chars().last(), Some('\0'));
|
assert_eq!(function.chars().last(), Some('\0'));
|
||||||
|
|
||||||
let module = unsafe { LoadLibraryA(library.as_ptr()) };
|
let module = unsafe { LoadLibraryA(library.as_ptr()) };
|
||||||
if module == 0 {
|
if module == 0 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
Some(unsafe { GetProcAddress(module, function.as_ptr()) })
|
Some(unsafe { GetProcAddress(module, function.as_ptr()) })
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! get_function {
|
macro_rules! get_function {
|
||||||
($lib:expr, $func:ident) => {
|
($lib:expr, $func:ident) => {
|
||||||
get_function_impl(concat!($lib, '\0'), concat!(stringify!($func), '\0')).map(|f| unsafe {
|
get_function_impl(concat!($lib, '\0'), concat!(stringify!($func), '\0')).map(|f| unsafe {
|
||||||
std::mem::transmute::<::windows_sys::Win32::Foundation::FARPROC, $func>(f)
|
std::mem::transmute::<::windows_sys::Win32::Foundation::FARPROC, $func>(f)
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a tuple of (major, minor, buildnumber)
|
/// Returns a tuple of (major, minor, buildnumber)
|
||||||
fn get_windows_ver() -> Option<(u32, u32, u32)> {
|
fn get_windows_ver() -> Option<(u32, u32, u32)> {
|
||||||
type RtlGetVersion = unsafe extern "system" fn(*mut OSVERSIONINFOW) -> i32;
|
type RtlGetVersion = unsafe extern "system" fn(*mut OSVERSIONINFOW) -> i32;
|
||||||
let handle = get_function!("ntdll.dll", RtlGetVersion);
|
let handle = get_function!("ntdll.dll", RtlGetVersion);
|
||||||
if let Some(rtl_get_version) = handle {
|
if let Some(rtl_get_version) = handle {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut vi = OSVERSIONINFOW {
|
let mut vi = OSVERSIONINFOW {
|
||||||
dwOSVersionInfoSize: 0,
|
dwOSVersionInfoSize: 0,
|
||||||
dwMajorVersion: 0,
|
dwMajorVersion: 0,
|
||||||
dwMinorVersion: 0,
|
dwMinorVersion: 0,
|
||||||
dwBuildNumber: 0,
|
dwBuildNumber: 0,
|
||||||
dwPlatformId: 0,
|
dwPlatformId: 0,
|
||||||
szCSDVersion: [0; 128],
|
szCSDVersion: [0; 128],
|
||||||
};
|
};
|
||||||
|
|
||||||
let status = (rtl_get_version)(&mut vi as _);
|
let status = (rtl_get_version)(&mut vi as _);
|
||||||
|
|
||||||
if status >= 0 {
|
if status >= 0 {
|
||||||
Some((vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber))
|
Some((vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_win11() -> bool {
|
pub fn is_win11() -> bool {
|
||||||
let v = get_windows_ver().unwrap_or_default();
|
let v = get_windows_ver().unwrap_or_default();
|
||||||
v.2 >= 22000
|
v.2 >= 22000
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_version() {
|
fn test_version() {
|
||||||
dbg!(get_windows_ver().unwrap_or_default());
|
dbg!(get_windows_ver().unwrap_or_default());
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,13 +25,8 @@
|
||||||
"icons/icon-new.icns",
|
"icons/icon-new.icns",
|
||||||
"icons/icon.ico"
|
"icons/icon.ico"
|
||||||
],
|
],
|
||||||
"resources": [
|
"resources": ["resources"],
|
||||||
"resources"
|
"externalBin": ["sidecar/clash", "sidecar/clash-meta"],
|
||||||
],
|
|
||||||
"externalBin": [
|
|
||||||
"sidecar/clash",
|
|
||||||
"sidecar/clash-meta"
|
|
||||||
],
|
|
||||||
"copyright": "© 2022 zzzgydi All Rights Reserved",
|
"copyright": "© 2022 zzzgydi All Rights Reserved",
|
||||||
"category": "DeveloperTool",
|
"category": "DeveloperTool",
|
||||||
"shortDescription": "A Clash GUI based on tauri.",
|
"shortDescription": "A Clash GUI based on tauri.",
|
||||||
|
@ -51,10 +46,7 @@
|
||||||
"digestAlgorithm": "sha256",
|
"digestAlgorithm": "sha256",
|
||||||
"timestampUrl": "",
|
"timestampUrl": "",
|
||||||
"wix": {
|
"wix": {
|
||||||
"language": [
|
"language": ["zh-CN", "en-US"]
|
||||||
"zh-CN",
|
|
||||||
"en-US"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -86,4 +78,4 @@
|
||||||
"csp": "script-src 'unsafe-eval' 'self'; default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'; img-src data: 'self';"
|
"csp": "script-src 'unsafe-eval' 'self'; default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'; img-src data: 'self';"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user