refactor: wip

This commit is contained in:
GyDi 2022-11-14 01:26:33 +08:00
parent fd6633f536
commit b03c52a501
No known key found for this signature in database
GPG Key ID: 9C3AD40F1F99880A
32 changed files with 2704 additions and 880 deletions

8
src-tauri/Cargo.lock generated
View File

@ -488,9 +488,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.22"
version = "0.4.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
dependencies = [
"iana-time-zone",
"js-sys",
@ -1860,9 +1860,9 @@ dependencies = [
[[package]]
name = "image"
version = "0.24.4"
version = "0.24.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd8e4fb07cf672b1642304e731ef8a6a4c7891d67bb4fd4f5ce58cd6ed86803c"
checksum = "69b7ea949b537b0fd0af141fff8c77690f2ce96f4f41f042ccb6c69c6c965945"
dependencies = [
"bytemuck",
"byteorder",

View File

@ -1,9 +1,10 @@
use crate::{
core::Core,
data::{ClashInfo, Data, PrfItem, PrfOption, Profiles, Verge},
config::*,
core::*,
feat,
utils::{dirs, help},
};
use crate::{log_if_err, ret_err, wrap_err};
use crate::{ret_err, wrap_err};
use anyhow::Result;
use serde_yaml::Mapping;
use std::collections::{HashMap, VecDeque};
@ -11,248 +12,189 @@ use sysproxy::Sysproxy;
type CmdResult<T = ()> = Result<T, String>;
/// get all profiles from `profiles.yaml`
#[tauri::command]
pub fn get_profiles() -> CmdResult<Profiles> {
let global = Data::global();
let profiles = global.profiles.lock();
pub fn get_profiles() -> CmdResult<IProfiles> {
let profiles = ProfilesN::global().config.lock();
Ok(profiles.clone())
}
/// manually exec enhanced profile
#[tauri::command]
pub fn enhance_profiles() -> CmdResult {
let core = Core::global();
wrap_err!(core.activate())
pub async fn enhance_profiles() -> CmdResult {
wrap_err!(CoreManager::global().activate_config().await)
}
/// import the profile from url
/// and save to `profiles.yaml`
#[deprecated]
#[tauri::command]
pub async fn import_profile(url: String, option: Option<PrfOption>) -> CmdResult {
let item = wrap_err!(PrfItem::from_url(&url, None, None, option).await)?;
let global = Data::global();
let mut profiles = global.profiles.lock();
let mut profiles = ProfilesN::global().config.lock();
wrap_err!(profiles.append_item(item))
}
/// new a profile
/// append a temp profile item file to the `profiles` dir
/// view the temp profile file by using vscode or other editor
#[tauri::command]
pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResult {
let item = wrap_err!(PrfItem::from(item, file_data).await)?;
let global = Data::global();
let mut profiles = global.profiles.lock();
let mut profiles = ProfilesN::global().config.lock();
wrap_err!(profiles.append_item(item))
}
/// Update the profile
#[tauri::command]
pub async fn update_profile(index: String, option: Option<PrfOption>) -> CmdResult {
let core = Core::global();
wrap_err!(core.update_profile_item(index, option).await)
wrap_err!(ProfilesN::global().update_item(index, option).await)
}
/// change the current profile
#[tauri::command]
pub fn select_profile(index: String) -> CmdResult {
let global = Data::global();
let mut profiles = global.profiles.lock();
let mut profiles = ProfilesN::global().config.lock();
wrap_err!(profiles.put_current(index))?;
drop(profiles);
let core = Core::global();
wrap_err!(core.activate())
wrap_err!(tauri::async_runtime::block_on(async {
CoreManager::global().activate_config().await
}))
}
/// change the profile chain
#[tauri::command]
pub fn change_profile_chain(chain: Option<Vec<String>>) -> CmdResult {
let global = Data::global();
let mut profiles = global.profiles.lock();
let mut profiles = ProfilesN::global().config.lock();
wrap_err!(profiles.put_chain(chain))?;
drop(profiles);
let core = Core::global();
wrap_err!(core.activate())
wrap_err!(tauri::async_runtime::block_on(async {
CoreManager::global().activate_config().await
}))
}
/// change the profile valid fields
#[tauri::command]
pub fn change_profile_valid(valid: Option<Vec<String>>) -> CmdResult {
let global = Data::global();
let mut profiles = global.profiles.lock();
let mut profiles = ProfilesN::global().config.lock();
wrap_err!(profiles.put_valid(valid))?;
drop(profiles);
let core = Core::global();
wrap_err!(core.activate())
wrap_err!(tauri::async_runtime::block_on(async {
CoreManager::global().activate_config().await
}))
}
/// delete profile item
#[tauri::command]
pub fn delete_profile(index: String) -> CmdResult {
let global = Data::global();
let mut profiles = global.profiles.lock();
let mut profiles = ProfilesN::global().config.lock();
if wrap_err!(profiles.delete_item(index))? {
drop(profiles);
let core = Core::global();
log_if_err!(core.activate());
wrap_err!(tauri::async_runtime::block_on(async {
CoreManager::global().activate_config().await
}))?;
}
Ok(())
}
/// patch the profile config
#[tauri::command]
pub fn patch_profile(index: String, profile: PrfItem) -> CmdResult {
let global = Data::global();
let mut profiles = global.profiles.lock();
let mut profiles = ProfilesN::global().config.lock();
wrap_err!(profiles.patch_item(index, profile))?;
drop(profiles);
// update cron task
let core = Core::global();
let mut timer = core.timer.lock();
wrap_err!(timer.refresh())
wrap_err!(timer::Timer::global().refresh())
}
/// run vscode command to edit the profile
#[tauri::command]
pub fn view_profile(index: String) -> CmdResult {
let global = Data::global();
let profiles = global.profiles.lock();
let profiles = ProfilesN::global().config.lock();
let item = wrap_err!(profiles.get_item(&index))?;
let file = item.file.clone();
if file.is_none() {
ret_err!("file is null");
}
let path = dirs::app_profiles_dir().join(file.unwrap());
let file = item.file.clone().ok_or("the file field is null")?;
let path = dirs::app_profiles_dir().join(file);
if !path.exists() {
ret_err!("file not found");
ret_err!("the file not found");
}
wrap_err!(help::open_file(path))
}
/// read the profile item file data
#[tauri::command]
pub fn read_profile_file(index: String) -> CmdResult<String> {
let global = Data::global();
let profiles = global.profiles.lock();
let profiles = ProfilesN::global().config.lock();
let item = wrap_err!(profiles.get_item(&index))?;
let data = wrap_err!(item.read_file())?;
Ok(data)
}
/// save the profile item file data
#[tauri::command]
pub fn save_profile_file(index: String, file_data: Option<String>) -> CmdResult {
if file_data.is_none() {
return Ok(());
}
let global = Data::global();
let profiles = global.profiles.lock();
let profiles = ProfilesN::global().config.lock();
let item = wrap_err!(profiles.get_item(&index))?;
wrap_err!(item.save_file(file_data.unwrap()))
}
/// get the clash core info from the state
/// the caller can also get the infomation by clash's api
#[tauri::command]
pub fn get_clash_info() -> CmdResult<ClashInfo> {
let global = Data::global();
let clash = global.clash.lock();
Ok(clash.info.clone())
pub fn get_clash_info() -> CmdResult<ClashInfoN> {
Ok(ClashN::global().info.lock().clone())
}
/// get the runtime clash config mapping
#[tauri::command]
pub fn get_runtime_config() -> CmdResult<Option<Mapping>> {
let core = Core::global();
let rt = core.runtime.lock();
Ok(rt.config.clone())
Ok(CoreManager::global().runtime_config.lock().config.clone())
}
/// get the runtime clash config yaml string
#[tauri::command]
pub fn get_runtime_yaml() -> CmdResult<Option<String>> {
let core = Core::global();
let rt = core.runtime.lock();
Ok(rt.config_yaml.clone())
Ok(CoreManager::global()
.runtime_config
.lock()
.config_yaml
.clone())
}
/// get the runtime config exists keys
#[tauri::command]
pub fn get_runtime_exists() -> CmdResult<Vec<String>> {
let core = Core::global();
let rt = core.runtime.lock();
Ok(rt.exists_keys.clone())
Ok(CoreManager::global()
.runtime_config
.lock()
.exists_keys
.clone())
}
/// get the runtime enhanced chain log
#[tauri::command]
pub fn get_runtime_logs() -> CmdResult<HashMap<String, Vec<(String, String)>>> {
let core = Core::global();
let rt = core.runtime.lock();
Ok(rt.chain_logs.clone())
Ok(CoreManager::global()
.runtime_config
.lock()
.chain_logs
.clone())
}
/// update the clash core config
/// after putting the change to the clash core
/// then we should save the latest config
#[tauri::command]
pub fn patch_clash_config(payload: Mapping) -> CmdResult {
let core = Core::global();
wrap_err!(core.patch_clash(payload))
wrap_err!(feat::patch_clash(payload))
}
#[tauri::command]
pub fn get_verge_config() -> CmdResult<Verge> {
let global = Data::global();
let verge = global.verge.lock();
Ok(verge.clone())
}
/// patch the verge config
/// this command only save the config and not responsible for other things
#[tauri::command]
pub fn patch_verge_config(payload: Verge) -> CmdResult {
let core = Core::global();
wrap_err!(core.patch_verge(payload))
pub fn get_verge_config() -> CmdResult<IVerge> {
Ok(VergeN::global().config.lock().clone())
}
#[tauri::command]
pub fn update_hotkeys(hotkeys: Vec<String>) -> CmdResult {
let core = Core::global();
let mut hotkey = core.hotkey.lock();
wrap_err!(hotkey.update(hotkeys))
pub fn patch_verge_config(payload: IVerge) -> CmdResult {
wrap_err!(feat::patch_verge(payload))
}
/// change clash core
#[tauri::command]
pub fn change_clash_core(clash_core: Option<String>) -> CmdResult {
let core = Core::global();
wrap_err!(core.change_core(clash_core))
wrap_err!(tauri::async_runtime::block_on(async {
CoreManager::global().change_core(clash_core).await
}))
}
/// restart the sidecar
#[tauri::command]
pub fn restart_sidecar() -> CmdResult {
let core = Core::global();
wrap_err!(core.restart_clash())
}
/// kill all sidecars when update app
#[tauri::command]
pub fn kill_sidecar() {
tauri::api::process::kill_children();
wrap_err!(CoreManager::global().run_core())
}
/// get the system proxy
@ -273,44 +215,38 @@ pub fn get_sys_proxy() -> CmdResult<Mapping> {
#[tauri::command]
pub fn get_clash_logs() -> CmdResult<VecDeque<String>> {
let core = Core::global();
let service = core.service.lock();
Ok(service.get_logs())
Ok(logger::Logger::global().get_log())
}
/// open app config dir
#[tauri::command]
pub fn open_app_dir() -> CmdResult<()> {
let app_dir = dirs::app_home_dir();
wrap_err!(open::that(app_dir))
}
/// open logs dir
#[tauri::command]
pub fn open_logs_dir() -> CmdResult<()> {
let log_dir = dirs::app_logs_dir();
wrap_err!(open::that(log_dir))
}
/// open url
#[tauri::command]
pub fn open_web_url(url: String) -> CmdResult<()> {
wrap_err!(open::that(url))
}
/// service mode
#[cfg(windows)]
pub mod service {
use super::*;
use crate::core::win_service::JsonResponse;
#[tauri::command]
pub async fn start_service() -> CmdResult<()> {
pub async fn start_service() -> CmdResult {
wrap_err!(crate::core::Service::start_service().await)
}
#[tauri::command]
pub async fn stop_service() -> CmdResult<()> {
pub async fn stop_service() -> CmdResult {
wrap_err!(crate::core::Service::stop_service().await)
}
@ -324,12 +260,12 @@ pub mod service {
}
#[tauri::command]
pub async fn install_service() -> CmdResult<()> {
pub async fn install_service() -> CmdResult {
wrap_err!(crate::core::Service::install_service().await)
}
#[tauri::command]
pub async fn uninstall_service() -> CmdResult<()> {
pub async fn uninstall_service() -> CmdResult {
wrap_err!(crate::core::Service::uninstall_service().await)
}
}
@ -339,23 +275,23 @@ pub mod service {
use super::*;
#[tauri::command]
pub async fn start_service() -> CmdResult<()> {
pub async fn start_service() -> CmdResult {
Ok(())
}
#[tauri::command]
pub async fn stop_service() -> CmdResult<()> {
pub async fn stop_service() -> CmdResult {
Ok(())
}
#[tauri::command]
pub async fn check_service() -> CmdResult<()> {
pub async fn check_service() -> CmdResult {
Ok(())
}
#[tauri::command]
pub async fn install_service() -> CmdResult<()> {
pub async fn install_service() -> CmdResult {
Ok(())
}
#[tauri::command]
pub async fn uninstall_service() -> CmdResult<()> {
pub async fn uninstall_service() -> CmdResult {
Ok(())
}
}

View File

@ -0,0 +1,180 @@
use crate::utils::{config, dirs};
use anyhow::Result;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
use serde_yaml::{Mapping, Value};
use std::sync::Arc;
#[derive(Debug)]
pub struct ClashN {
/// maintain the clash config
pub config: Arc<Mutex<Mapping>>,
/// some info
pub info: Arc<Mutex<ClashInfoN>>,
}
impl ClashN {
pub fn global() -> &'static ClashN {
static DATA: OnceCell<ClashN> = OnceCell::new();
DATA.get_or_init(|| {
let config = ClashN::read_config();
let info = ClashInfoN::from(&config);
ClashN {
config: Arc::new(Mutex::new(config)),
info: Arc::new(Mutex::new(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<()> {
let config = self.config.lock();
config::save_yaml(
dirs::clash_path(),
&*config,
Some("# Default Config For ClashN Core\n\n"),
)
}
/// 返回旧值
pub fn patch_info(&self, info: ClashInfoN) -> Result<ClashInfoN> {
let mut old_info = self.info.lock();
let old = (*old_info).to_owned();
*old_info = info;
Ok(old)
}
/// patch update the clash config
/// if the port is changed then return true
pub fn patch_config(&self, patch: Mapping) -> Result<()> {
let mut config = self.config.lock();
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() {
config.insert(key, value);
}
if change_info {
let mut info = self.info.lock();
*info = ClashInfoN::from(&*config);
}
self.save_config()
}
}
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct ClashInfoN {
/// clash sidecar status
pub status: String,
/// clash core port
pub port: Option<String>,
/// same as `external-controller`
pub server: Option<String>,
/// clash secret
pub secret: Option<String>,
}
impl ClashInfoN {
/// parse the clash's config.yaml
/// get some information
pub fn from(config: &Mapping) -> ClashInfoN {
let key_port_1 = Value::from("mixed-port");
let key_port_2 = Value::from("port");
let key_server = Value::from("external-controller");
let key_secret = Value::from("secret");
let mut status: u32 = 0;
let port = match config.get(&key_port_1) {
Some(value) => match value {
Value::String(val_str) => Some(val_str.clone()),
Value::Number(val_num) => Some(val_num.to_string()),
_ => {
status |= 0b1;
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,
};
ClashInfoN {
status: format!("{status}"),
port,
server,
secret,
}
}
}

View File

@ -1,69 +1,9 @@
mod field;
mod merge;
mod script;
mod tun;
mod clash;
mod prfitem;
mod profiles;
mod verge;
pub(self) use self::field::*;
use self::merge::*;
use self::script::*;
use self::tun::*;
use crate::data::ChainItem;
use crate::data::ChainType;
use serde_yaml::Mapping;
use std::collections::HashMap;
use std::collections::HashSet;
type ResultLog = Vec<(String, String)>;
pub fn enhance_config(
clash_config: Mapping,
profile_config: Mapping,
chain: Vec<ChainItem>,
valid: Vec<String>,
tun_mode: bool,
) -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
let mut config = profile_config;
let mut result_map = HashMap::new();
let mut exists_keys = use_keys(&config);
let valid = use_valid_fields(valid);
chain.into_iter().for_each(|item| match item.data {
ChainType::Merge(merge) => {
exists_keys.extend(use_keys(&merge));
config = use_merge(merge, config.to_owned());
config = use_filter(config.to_owned(), &valid);
}
ChainType::Script(script) => {
let mut logs = vec![];
match use_script(script, config.to_owned()) {
Ok((res_config, res_logs)) => {
exists_keys.extend(use_keys(&res_config));
config = use_filter(res_config, &valid);
logs.extend(res_logs);
}
Err(err) => logs.push(("exception".into(), err.to_string())),
}
result_map.insert(item.uid, logs);
}
});
config = use_filter(config, &valid);
for (key, value) in clash_config.into_iter() {
config.insert(key, value);
}
let clash_fields = use_clash_fields();
config = use_filter(config, &clash_fields);
config = use_tun(config, tun_mode);
config = use_sort(config);
let mut exists_set = HashSet::new();
exists_set.extend(exists_keys.into_iter().filter(|s| clash_fields.contains(s)));
exists_keys = exists_set.into_iter().collect();
(config, exists_keys, result_map)
}
pub use self::clash::*;
pub use self::prfitem::*;
pub use self::profiles::*;
pub use self::verge::*;

View File

@ -0,0 +1,406 @@
use crate::utils::{config, dirs, help, tmpl};
use anyhow::{bail, Context, Result};
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
use serde_yaml::Mapping;
use std::fs;
use sysproxy::Sysproxy;
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct PrfItem {
pub uid: Option<String>,
/// profile item type
/// enum value: remote | local | script | merge
#[serde(rename = "type")]
pub itype: Option<String>,
/// profile name
pub name: Option<String>,
/// profile file
pub file: Option<String>,
/// profile description
#[serde(skip_serializing_if = "Option::is_none")]
pub desc: Option<String>,
/// source url
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
/// selected information
#[serde(skip_serializing_if = "Option::is_none")]
pub selected: Option<Vec<PrfSelected>>,
/// subscription user info
#[serde(skip_serializing_if = "Option::is_none")]
pub extra: Option<PrfExtra>,
/// updated time
pub updated: Option<usize>,
/// some options of the item
#[serde(skip_serializing_if = "Option::is_none")]
pub option: Option<PrfOption>,
/// the file data
#[serde(skip)]
pub file_data: Option<String>,
}
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct PrfSelected {
pub name: Option<String>,
pub now: Option<String>,
}
#[derive(Default, Debug, Clone, Copy, Deserialize, Serialize)]
pub struct PrfExtra {
pub upload: usize,
pub download: usize,
pub total: usize,
pub expire: usize,
}
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct PrfOption {
/// for `remote` profile's http request
/// see issue #13
#[serde(skip_serializing_if = "Option::is_none")]
pub user_agent: Option<String>,
/// for `remote` profile
/// use system proxy
#[serde(skip_serializing_if = "Option::is_none")]
pub with_proxy: Option<bool>,
/// for `remote` profile
/// use self proxy
#[serde(skip_serializing_if = "Option::is_none")]
pub self_proxy: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub update_interval: Option<u64>,
}
impl PrfOption {
pub fn merge(one: Option<Self>, other: Option<Self>) -> Option<Self> {
match (one, other) {
(Some(mut a), Some(b)) => {
a.user_agent = b.user_agent.or(a.user_agent);
a.with_proxy = b.with_proxy.or(a.with_proxy);
a.self_proxy = b.self_proxy.or(a.self_proxy);
a.update_interval = b.update_interval.or(a.update_interval);
Some(a)
}
t @ _ => t.0.or(t.1),
}
}
}
impl Default for PrfItem {
fn default() -> Self {
PrfItem {
uid: None,
itype: None,
name: None,
desc: None,
file: None,
url: None,
selected: None,
extra: None,
updated: None,
option: None,
file_data: None,
}
}
}
impl PrfItem {
/// From partial item
/// must contain `itype`
pub async fn from(item: PrfItem, file_data: Option<String>) -> Result<PrfItem> {
if item.itype.is_none() {
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
/// create a new item from name/desc
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");
Ok(PrfItem {
uid: Some(uid),
itype: Some("local".into()),
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(file_data.unwrap_or(tmpl::ITEM_LOCAL.into())),
})
}
/// ## Remote type
/// create a new item from url
pub async fn from_url(
url: &str,
name: Option<String>,
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 clash = super::ClashN::global();
let port = clash.info.lock().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 };
let version = format!("clash-verge/{version}");
builder = builder.user_agent(user_agent.unwrap_or(version));
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),
})
}
/// ## Merge type (enhance)
/// 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),
itype: Some("merge".into()),
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()),
})
}
/// ## Script type (enhance)
/// create the enhanced item by using javascript quick.js
pub fn from_script(name: String, desc: String) -> Result<PrfItem> {
let uid = help::get_uid("s");
let file = format!("{uid}.js"); // js ext
Ok(PrfItem {
uid: Some(uid),
itype: Some("script".into()),
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_SCRIPT.into()),
})
}
/// get the file data
pub fn read_file(&self) -> Result<String> {
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::read_to_string(path).context("failed to read the file")
}
/// 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)]
pub struct ChainItem {
pub uid: String,
pub data: ChainType,
}
#[derive(Debug, Clone)]
pub enum ChainType {
Merge(Mapping),
Script(String),
}

View File

@ -0,0 +1,379 @@
use super::{prfitem::PrfItem, ChainItem, PrfOption};
use crate::{
core::CoreManager,
utils::{config, dirs, help},
};
use anyhow::{bail, Context, Result};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
use serde_yaml::Mapping;
use std::collections::HashMap;
use std::sync::Arc;
use std::{fs, io::Write};
pub struct ProfilesN {
pub config: Arc<Mutex<IProfiles>>,
}
impl ProfilesN {
pub fn global() -> &'static ProfilesN {
static PROFILES: OnceCell<ProfilesN> = OnceCell::new();
PROFILES.get_or_init(|| ProfilesN {
config: Arc::new(Mutex::new(IProfiles::read_file())),
})
}
/// 更新单个配置
pub async fn update_item(&self, uid: String, option: Option<PrfOption>) -> Result<()> {
let (url, opt) = {
let profiles = self.config.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);
tauri::async_runtime::block_on(async {
CoreManager::global().activate_config().await
})?;
}
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 = self.config.lock();
profiles.update_item(uid.clone(), item)?;
// reactivate the profile
if Some(uid) == profiles.get_current() {
drop(profiles);
tauri::async_runtime::block_on(async {
CoreManager::global().activate_config().await
})?;
}
Ok(())
}
}
/// Define the `profiles.yaml` schema
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct IProfiles {
/// same as PrfConfig.current
current: Option<String>,
/// same as PrfConfig.chain
chain: Option<Vec<String>>,
/// record valid fields for clash
valid: Option<Vec<String>>,
/// profile list
items: Option<Vec<PrfItem>>,
}
macro_rules! patch {
($lv: expr, $rv: expr, $key: tt) => {
if ($rv.$key).is_some() {
$lv.$key = $rv.$key;
}
};
}
impl IProfiles {
/// 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![]);
}
// compatible with the old old old version
profiles.items.as_mut().map(|items| {
for mut item in items.iter_mut() {
if item.uid.is_none() {
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() {
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 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()
}
/// 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);
}
});
}
// 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![]);
Ok(PrfActivate {
current,
chain,
valid,
})
}
}
#[derive(Default, Clone)]
pub struct PrfActivate {
pub current: Mapping,
pub chain: Vec<ChainItem>,
pub valid: Vec<String>,
}
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct RuntimeResult {
pub config: Option<Mapping>,
pub config_yaml: Option<String>,
// 记录在配置中包括merge和script生成的出现过的keys
// 这些keys不一定都生效
pub exists_keys: Vec<String>,
pub chain_logs: HashMap<String, Vec<(String, String)>>,
}

View File

@ -0,0 +1,166 @@
use crate::utils::{config, dirs};
use anyhow::Result;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
pub struct VergeN {
pub config: Arc<Mutex<IVerge>>,
}
impl VergeN {
pub fn global() -> &'static VergeN {
static DATA: OnceCell<VergeN> = OnceCell::new();
DATA.get_or_init(|| {
let config = config::read_yaml::<IVerge>(dirs::verge_path());
VergeN {
config: Arc::new(Mutex::new(config)),
}
})
}
/// Save IVerge App Config
pub fn save_file(&self) -> Result<()> {
let config = self.config.lock();
config::save_yaml(
dirs::verge_path(),
&*config,
Some("# The Config for Clash IVerge App\n\n"),
)
}
/// patch verge config
/// only save to file
pub fn patch_config(&self, patch: IVerge) -> Result<()> {
let mut config = self.config.lock();
macro_rules! patch {
($key: tt) => {
if patch.$key.is_some() {
config.$key = patch.$key;
}
};
}
patch!(language);
patch!(theme_mode);
patch!(theme_blur);
patch!(traffic_graph);
patch!(enable_tun_mode);
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);
patch!(theme_setting);
patch!(web_ui_list);
patch!(clash_core);
patch!(hotkeys);
patch!(auto_close_connection);
patch!(default_latency_test);
self.save_file()
}
/// 在初始化前尝试拿到单例端口的值
pub fn get_singleton_port() -> u16 {
let config = config::read_yaml::<IVerge>(dirs::verge_path());
#[cfg(not(feature = "verge-dev"))]
const SERVER_PORT: u16 = 33331;
#[cfg(feature = "verge-dev")]
const SERVER_PORT: u16 = 11233;
config.app_singleton_port.unwrap_or(SERVER_PORT)
}
}
/// ### `verge.yaml` schema
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct IVerge {
/// app listening port
/// for app singleton
pub app_singleton_port: Option<u16>,
// i18n
pub language: Option<String>,
/// `light` or `dark` or `system`
pub theme_mode: Option<String>,
/// enable blur mode
/// maybe be able to set the alpha
pub theme_blur: Option<bool>,
/// enable traffic graph default is true
pub traffic_graph: Option<bool>,
/// clash tun mode
pub enable_tun_mode: Option<bool>,
/// windows service mode
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_service_mode: Option<bool>,
/// can the app auto startup
pub enable_auto_launch: Option<bool>,
/// not show the window on launch
pub enable_silent_start: Option<bool>,
/// set system proxy
pub enable_system_proxy: Option<bool>,
/// enable proxy guard
pub enable_proxy_guard: Option<bool>,
/// set system proxy bypass
pub system_proxy_bypass: Option<String>,
/// proxy guard duration
pub proxy_guard_duration: Option<u64>,
/// theme setting
pub theme_setting: Option<IVergeTheme>,
/// web ui list
pub web_ui_list: Option<Vec<String>>,
/// clash core path
#[serde(skip_serializing_if = "Option::is_none")]
pub clash_core: Option<String>,
/// hotkey map
/// format: {func},{key}
pub hotkeys: Option<Vec<String>>,
/// 切换代理时自动关闭连接
pub auto_close_connection: Option<bool>,
/// 默认的延迟测试连接
pub default_latency_test: Option<String>,
}
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct IVergeTheme {
pub primary_color: Option<String>,
pub secondary_color: Option<String>,
pub primary_text: Option<String>,
pub secondary_text: Option<String>,
pub info_color: Option<String>,
pub error_color: Option<String>,
pub warning_color: Option<String>,
pub success_color: Option<String>,
pub font_family: Option<String>,
pub css_injection: Option<String>,
}

View File

@ -0,0 +1,66 @@
use crate::{config, utils::dirs};
use anyhow::{bail, Result};
use reqwest::header::HeaderMap;
use serde_yaml::Mapping;
use std::collections::HashMap;
/// PUT /configs
pub async fn put_configs() -> Result<()> {
let (url, headers) = clash_client_info()?;
let url = format!("{url}/configs");
let runtime_yaml = dirs::clash_runtime_yaml();
let runtime_yaml = dirs::path_to_str(&runtime_yaml)?;
let mut data = HashMap::new();
data.insert("path", runtime_yaml);
let client = reqwest::ClientBuilder::new().no_proxy().build()?;
let builder = client.put(&url).headers(headers).json(&data);
let response = builder.send().await?;
match response.status().as_u16() {
204 => Ok(()),
status @ _ => {
bail!("failed to put configs with status \"{status}\"")
}
}
}
/// PATCH /configs
pub async fn patch_configs(config: &Mapping) -> Result<()> {
let (url, headers) = clash_client_info()?;
let url = format!("{url}/configs");
let client = reqwest::ClientBuilder::new().no_proxy().build()?;
let builder = client.patch(&url).headers(headers.clone()).json(config);
builder.send().await?;
Ok(())
}
/// 根据clash info获取clash服务地址和请求头
fn clash_client_info() -> Result<(String, HeaderMap)> {
let info = { config::ClashN::global().info.lock().clone() };
if info.server.is_none() {
let status = &info.status;
if info.port.is_none() {
bail!("failed to parse config.yaml file with status {status}");
} else {
bail!("failed to parse the server with status {status}");
}
}
let server = info.server.unwrap();
let server = format!("http://{server}");
let mut headers = HeaderMap::new();
headers.insert("Content-Type", "application/json".parse()?);
if let Some(secret) = info.secret.as_ref() {
let secret = format!("Bearer {}", secret.clone()).parse()?;
headers.insert("Authorization", secret);
}
Ok((server, headers))
}

262
src-tauri/src/core/core.rs Normal file
View File

@ -0,0 +1,262 @@
use super::{clash_api, logger::Logger};
use crate::{
config::*,
enhance,
utils::{self, dirs},
};
use anyhow::{bail, Context, Result};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use std::{fs, io::Write, sync::Arc, time::Duration};
use sysinfo::{Pid, PidExt, ProcessExt, System, SystemExt};
use tauri::api::process::{Command, CommandChild, CommandEvent};
use tokio::time::sleep;
#[derive(Debug)]
pub struct CoreManager {
clash_core: Arc<Mutex<String>>,
sidecar: Arc<Mutex<Option<CommandChild>>>,
#[allow(unused)]
use_service_mode: Arc<Mutex<bool>>,
pub runtime_config: Arc<Mutex<RuntimeResult>>,
}
impl CoreManager {
pub fn global() -> &'static CoreManager {
static CORE_MANAGER: OnceCell<CoreManager> = OnceCell::new();
CORE_MANAGER.get_or_init(|| CoreManager {
clash_core: Arc::new(Mutex::new("clash".into())),
sidecar: Arc::new(Mutex::new(None)),
runtime_config: Arc::new(Mutex::new(RuntimeResult::default())),
use_service_mode: Arc::new(Mutex::new(false)),
})
}
pub fn init(&self) -> Result<()> {
// kill old clash process
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();
system.process(Pid::from_u32(pid)).map(|proc| {
if proc.name().contains("clash") {
proc.kill();
}
});
}
}
// 使用配置的核心
let verge = VergeN::global().config.lock();
if let Some(verge_core) = verge.clash_core.as_ref() {
if verge_core == "clash" || verge_core == "clash-meta" {
let mut clash_core = self.clash_core.lock();
*clash_core = verge_core.clone();
}
}
// 启动clash
self.run_core()?;
// 更新配置
tauri::async_runtime::spawn(async {
sleep(Duration::from_millis(100)).await;
crate::log_err!(Self::global().activate_config().await);
});
Ok(())
}
/// 检查配置是否正确
pub fn check_config(&self) -> Result<()> {
let config_path = dirs::clash_runtime_yaml();
let config_path = dirs::path_to_str(&config_path)?;
let clash_core = { self.clash_core.lock().clone() };
let output = Command::new_sidecar(clash_core)?
.args(["-t", config_path])
.output()?;
if !output.status.success() {
Logger::global().set_log(output.stderr.clone());
bail!("{}", output.stderr); // 过滤掉终端颜色值
}
Ok(())
}
/// 启动核心
pub fn run_core(&self) -> Result<()> {
// 先纠正重要的配置字段
self.correct_config()?;
let mut sidecar = self.sidecar.lock();
if let Some(child) = sidecar.take() {
let _ = child.kill();
}
let app_dir = dirs::app_home_dir();
let app_dir = dirs::path_to_str(&app_dir)?;
let clash_core = { self.clash_core.lock().clone() };
// fix #212
let args = match clash_core.as_str() {
"clash-meta" => vec!["-m", "-d", app_dir],
_ => vec!["-d", app_dir],
};
let cmd = Command::new_sidecar(clash_core)?;
let (mut rx, cmd_child) = cmd.args(args).spawn()?;
// 将pid写入文件中
crate::log_err!({
let pid = cmd_child.pid();
let path = dirs::clash_pid_path();
fs::File::create(path)
.context("failed to create the pid file")?
.write(format!("{pid}").as_bytes())
.context("failed to write pid to the file")?;
<Result<()>>::Ok(())
});
*sidecar = Some(cmd_child);
tauri::async_runtime::spawn(async move {
while let Some(event) = rx.recv().await {
match event {
CommandEvent::Stdout(line) => {
let can_short = line.starts_with("time=") && line.len() > 33;
let stdout = if can_short { &line[33..] } else { &line };
log::info!(target: "app" ,"[clash]: {}", stdout);
Logger::global().set_log(line);
}
CommandEvent::Stderr(err) => {
log::error!(target: "app" ,"[clash error]: {err}");
Logger::global().set_log(err);
}
CommandEvent::Error(err) => {
log::error!(target: "app" ,"[clash error]: {err}");
Logger::global().set_log(err);
}
CommandEvent::Terminated(_) => {
log::info!(target: "app" ,"clash core Terminated");
break;
}
_ => {}
}
}
});
Ok(())
}
/// 停止核心运行
pub fn stop_core(&self) -> Result<()> {
let mut sidecar = self.sidecar.lock();
if let Some(child) = sidecar.take() {
let _ = child.kill();
}
Ok(())
}
/// 切换核心
pub async fn change_core(&self, clash_core: Option<String>) -> Result<()> {
let clash_core = clash_core.ok_or(anyhow::anyhow!("clash core is null"))?;
if &clash_core != "clash" && &clash_core != "clash-meta" {
bail!("invalid clash core name \"{clash_core}\"");
}
// 清掉旧日志
Logger::global().clear_log();
let mut self_core = self.clash_core.lock();
let old_core = self_core.clone(); // 保存一下旧值
*self_core = clash_core.clone();
drop(self_core);
match self.run_core() {
Ok(_) => {
// 更新到配置文件
let mut verge = VergeN::global().config.lock();
verge.clash_core = Some(clash_core);
drop(verge);
let _ = VergeN::global().save_file();
sleep(Duration::from_millis(100)).await; // 等一会儿再更新配置
self.activate_config().await?;
Ok(())
}
Err(err) => {
// 恢复旧的值
let mut self_core = self.clash_core.lock();
*self_core = old_core;
Err(err)
}
}
}
/// 纠正一下配置
/// 将mixed-port和external-controller都改为配置的内容
pub fn correct_config(&self) -> Result<()> {
// todo!()
Ok(())
}
/// 激活一个配置
pub async fn activate_config(&self) -> Result<()> {
let clash_config = { ClashN::global().config.lock().clone() };
let tun_mode = { VergeN::global().config.lock().enable_tun_mode.clone() };
let tun_mode = tun_mode.unwrap_or(false);
let pa = { ProfilesN::global().config.lock().gen_activate()? };
let (config, exists_keys, logs) =
enhance::enhance_config(clash_config, pa.current, pa.chain, pa.valid, tun_mode);
// 保存到文件中
let runtime_path = dirs::clash_runtime_yaml();
utils::config::save_yaml(runtime_path, &config, Some("# Clash Verge Runtime Config"))?;
// 检查配置是否正常
self.check_config()?;
// todo 是否需要检查核心是否运行
// 发送请求 发送5次
for i in 0..5 {
match clash_api::put_configs().await {
Ok(_) => break,
Err(err) => {
if i < 4 {
log::error!(target: "app", "{err}");
} else {
bail!(err);
}
}
}
sleep(Duration::from_millis(250)).await;
}
// 保存结果
let mut runtime = self.runtime_config.lock();
let config_yaml = Some(serde_yaml::to_string(&config).unwrap_or("".into()));
*runtime = RuntimeResult {
config: Some(config),
config_yaml,
exists_keys,
chain_logs: logs,
};
Ok(())
}
}

View File

@ -0,0 +1,217 @@
#![cfg(target_os = "windows")]
use crate::utils::{config, dirs};
use anyhow::Context;
use deelevate::{PrivilegeLevel, Token};
use runas::Command as RunasCommand;
use serde::{Deserialize, Serialize};
use std::os::windows::process::CommandExt;
use std::{env::current_exe, process::Command as StdCommand};
const SERVICE_NAME: &str = "clash_verge_service";
const SERVICE_URL: &str = "http://127.0.0.1:33211";
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ResponseBody {
pub bin_path: String,
pub config_dir: String,
pub log_file: String,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct JsonResponse {
pub code: u64,
pub msg: String,
pub data: Option<ResponseBody>,
}
impl Service {
/// Install the Clash Verge Service
/// 该函数应该在协程或者线程中执行避免UAC弹窗阻塞主线程
pub async fn install_service() -> Result<()> {
let binary_path = dirs::service_path();
let install_path = binary_path.with_file_name("install-service.exe");
if !install_path.exists() {
bail!("installer exe not found");
}
let token = Token::with_current_process()?;
let level = token.privilege_level()?;
let status = match level {
PrivilegeLevel::NotPrivileged => RunasCommand::new(install_path).status()?,
_ => StdCommand::new(install_path)
.creation_flags(0x08000000)
.status()?,
};
if !status.success() {
bail!(
"failed to install service with status {}",
status.code().unwrap()
);
}
Ok(())
}
/// Uninstall the Clash Verge Service
/// 该函数应该在协程或者线程中执行避免UAC弹窗阻塞主线程
pub async fn uninstall_service() -> Result<()> {
let binary_path = dirs::service_path();
let uninstall_path = binary_path.with_file_name("uninstall-service.exe");
if !uninstall_path.exists() {
bail!("uninstaller exe not found");
}
let token = Token::with_current_process()?;
let level = token.privilege_level()?;
let status = match level {
PrivilegeLevel::NotPrivileged => RunasCommand::new(uninstall_path).status()?,
_ => StdCommand::new(uninstall_path)
.creation_flags(0x08000000)
.status()?,
};
if !status.success() {
bail!(
"failed to uninstall service with status {}",
status.code().unwrap()
);
}
Ok(())
}
/// [deprecated]
/// start service
/// 该函数应该在协程或者线程中执行避免UAC弹窗阻塞主线程
pub async fn start_service() -> Result<()> {
let token = Token::with_current_process()?;
let level = token.privilege_level()?;
let args = ["start", SERVICE_NAME];
let status = match level {
PrivilegeLevel::NotPrivileged => RunasCommand::new("sc").args(&args).status()?,
_ => StdCommand::new("sc").args(&args).status()?,
};
match status.success() {
true => Ok(()),
false => bail!(
"failed to start service with status {}",
status.code().unwrap()
),
}
}
/// stop service
pub async fn stop_service() -> Result<()> {
let url = format!("{SERVICE_URL}/stop_service");
let res = reqwest::ClientBuilder::new()
.no_proxy()
.build()?
.post(url)
.send()
.await?
.json::<JsonResponse>()
.await
.context("failed to connect to the Clash Verge Service")?;
if res.code != 0 {
bail!(res.msg);
}
Ok(())
}
/// check the windows service status
pub async fn check_service() -> Result<JsonResponse> {
let url = format!("{SERVICE_URL}/get_clash");
let response = reqwest::ClientBuilder::new()
.no_proxy()
.build()?
.get(url)
.send()
.await?
.json::<JsonResponse>()
.await
.context("failed to connect to the Clash Verge Service")?;
Ok(response)
}
/// start the clash by service
pub(super) async fn start_clash_by_service() -> Result<()> {
let status = Self::check_service().await?;
if status.code == 0 {
Self::stop_clash_by_service().await?;
sleep(Duration::from_secs(1)).await;
}
let clash_core = {
let global = Data::global();
let verge = global.verge.lock();
verge.clash_core.clone().unwrap_or("clash".into())
};
let clash_bin = format!("{clash_core}.exe");
let bin_path = current_exe().unwrap().with_file_name(clash_bin);
let bin_path = bin_path.as_os_str().to_str().unwrap();
let config_dir = dirs::app_home_dir();
let config_dir = config_dir.as_os_str().to_str().unwrap();
let log_path = dirs::service_log_file();
let log_path = log_path.as_os_str().to_str().unwrap();
let mut map = HashMap::new();
map.insert("bin_path", bin_path);
map.insert("config_dir", config_dir);
map.insert("log_file", log_path);
let url = format!("{SERVICE_URL}/start_clash");
let res = reqwest::ClientBuilder::new()
.no_proxy()
.build()?
.post(url)
.json(&map)
.send()
.await?
.json::<JsonResponse>()
.await
.context("failed to connect to the Clash Verge Service")?;
if res.code != 0 {
bail!(res.msg);
}
Ok(())
}
/// stop the clash by service
pub(super) async fn stop_clash_by_service() -> Result<()> {
let url = format!("{SERVICE_URL}/stop_clash");
let res = reqwest::ClientBuilder::new()
.no_proxy()
.build()?
.post(url)
.send()
.await?
.json::<JsonResponse>()
.await
.context("failed to connect to the Clash Verge Service")?;
if res.code != 0 {
bail!(res.msg);
}
Ok(())
}
}

View File

@ -1,65 +1,77 @@
use super::tray::Tray;
use crate::log_if_err;
use anyhow::{bail, Result};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use std::sync::Arc;
use tauri::{AppHandle, Manager, Window};
#[derive(Debug, Default, Clone)]
pub struct Handle {
pub app_handle: Option<AppHandle>,
pub app_handle: Arc<Mutex<Option<AppHandle>>>,
}
impl Handle {
pub fn set_inner(&mut self, app_handle: AppHandle) {
self.app_handle = Some(app_handle);
pub fn global() -> &'static Handle {
static HANDLE: OnceCell<Handle> = OnceCell::new();
HANDLE.get_or_init(|| Handle {
app_handle: Arc::new(Mutex::new(None)),
})
}
pub fn init(&self, app_handle: AppHandle) {
*self.app_handle.lock() = Some(app_handle);
}
pub fn get_window(&self) -> Option<Window> {
self.app_handle
.lock()
.as_ref()
.map_or(None, |a| a.get_window("main"))
}
pub fn refresh_clash(&self) {
if let Some(window) = self.get_window() {
pub fn refresh_clash() {
if let Some(window) = Self::global().get_window() {
log_if_err!(window.emit("verge://refresh-clash-config", "yes"));
}
}
pub fn refresh_verge(&self) {
if let Some(window) = self.get_window() {
pub fn refresh_verge() {
if let Some(window) = Self::global().get_window() {
log_if_err!(window.emit("verge://refresh-verge-config", "yes"));
}
}
#[allow(unused)]
pub fn refresh_profiles(&self) {
if let Some(window) = self.get_window() {
pub fn refresh_profiles() {
if let Some(window) = Self::global().get_window() {
log_if_err!(window.emit("verge://refresh-profiles-config", "yes"));
}
}
pub fn notice_message(&self, status: String, msg: String) {
if let Some(window) = self.get_window() {
log_if_err!(window.emit("verge://notice-message", (status, msg)));
pub fn notice_message<S: Into<String>, M: Into<String>>(status: S, msg: M) {
if let Some(window) = Self::global().get_window() {
log_if_err!(window.emit("verge://notice-message", (status.into(), msg.into())));
}
}
pub fn update_systray(&self) -> Result<()> {
if self.app_handle.is_none() {
bail!("update_systray unhandle error");
pub fn update_systray() -> Result<()> {
let app_handle = Self::global().app_handle.lock();
if app_handle.is_none() {
bail!("update_systray unhandled error");
}
let app_handle = self.app_handle.as_ref().unwrap();
Tray::update_systray(app_handle)?;
Tray::update_systray(app_handle.as_ref().unwrap())?;
Ok(())
}
/// update the system tray state
pub fn update_systray_part(&self) -> Result<()> {
if self.app_handle.is_none() {
bail!("update_systray unhandle error");
pub fn update_systray_part() -> Result<()> {
let app_handle = Self::global().app_handle.lock();
if app_handle.is_none() {
bail!("update_systray unhandled error");
}
let app_handle = self.app_handle.as_ref().unwrap();
Tray::update_part(app_handle)?;
Tray::update_part(app_handle.as_ref().unwrap())?;
Ok(())
}
}

View File

@ -1,25 +1,30 @@
use crate::{data::*, feat, log_if_err};
use crate::{config, feat, log_err};
use anyhow::{bail, Result};
use std::collections::HashMap;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use std::{collections::HashMap, sync::Arc};
use tauri::{AppHandle, GlobalShortcutManager};
pub struct Hotkey {
current: Vec<String>, // 保存当前的热键设置
manager: Option<AppHandle>,
current: Arc<Mutex<Vec<String>>>, // 保存当前的热键设置
app_handle: Arc<Mutex<Option<AppHandle>>>,
}
impl Hotkey {
pub fn new() -> Hotkey {
Hotkey {
current: Vec::new(),
manager: None,
}
pub fn global() -> &'static Hotkey {
static HOTKEY: OnceCell<Hotkey> = OnceCell::new();
HOTKEY.get_or_init(|| Hotkey {
current: Arc::new(Mutex::new(Vec::new())),
app_handle: Arc::new(Mutex::new(None)),
})
}
pub fn init(&mut self, app_handle: AppHandle) -> Result<()> {
self.manager = Some(app_handle);
let data = Data::global();
let verge = data.verge.lock();
pub fn init(&self, app_handle: AppHandle) -> Result<()> {
*self.app_handle.lock() = Some(app_handle);
let verge = config::VergeN::global().config.lock();
if let Some(hotkeys) = verge.hotkeys.as_ref() {
for hotkey in hotkeys.iter() {
@ -28,25 +33,26 @@ impl Hotkey {
let key = iter.next();
if func.is_some() && key.is_some() {
log_if_err!(self.register(key.unwrap(), func.unwrap()));
log_err!(self.register(key.unwrap(), func.unwrap()));
} else {
log::error!(target: "app", "invalid hotkey \"{}\":\"{}\"", key.unwrap_or("None"), func.unwrap_or("None"));
}
}
self.current = hotkeys.clone();
*self.current.lock() = hotkeys.clone();
}
Ok(())
}
fn get_manager(&self) -> Result<impl GlobalShortcutManager> {
if self.manager.is_none() {
let app_handle = self.app_handle.lock();
if app_handle.is_none() {
bail!("failed to get hotkey manager");
}
Ok(self.manager.as_ref().unwrap().global_shortcut_manager())
Ok(app_handle.as_ref().unwrap().global_shortcut_manager())
}
fn register(&mut self, hotkey: &str, func: &str) -> Result<()> {
fn register(&self, hotkey: &str, func: &str) -> Result<()> {
let mut manager = self.get_manager()?;
if manager.is_registered(hotkey)? {
@ -54,16 +60,16 @@ impl Hotkey {
}
let f = match func.trim() {
"clash_mode_rule" => || feat::change_clash_mode("rule"),
"clash_mode_global" => || feat::change_clash_mode("global"),
"clash_mode_direct" => || feat::change_clash_mode("direct"),
"clash_mode_script" => || feat::change_clash_mode("script"),
"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(),
"clash_mode_rule" => || feat::change_clash_mode("rule".into()),
"clash_mode_global" => || feat::change_clash_mode("global".into()),
"clash_mode_direct" => || feat::change_clash_mode("direct".into()),
"clash_mode_script" => || feat::change_clash_mode("script".into()),
"toggle_system_proxy" => || log_err!(feat::toggle_system_proxy()),
"enable_system_proxy" => || log_err!(feat::enable_system_proxy()),
"disable_system_proxy" => || log_err!(feat::disable_system_proxy()),
"toggle_tun_mode" => || log_err!(feat::toggle_tun_mode()),
"enable_tun_mode" => || log_err!(feat::enable_tun_mode()),
"disable_tun_mode" => || log_err!(feat::disable_tun_mode()),
_ => bail!("invalid function \"{func}\""),
};
@ -73,14 +79,14 @@ impl Hotkey {
Ok(())
}
fn unregister(&mut self, hotkey: &str) -> Result<()> {
fn unregister(&self, hotkey: &str) -> Result<()> {
self.get_manager()?.unregister(&hotkey)?;
log::info!(target: "app", "unregister hotkey {hotkey}");
Ok(())
}
pub fn update(&mut self, new_hotkeys: Vec<String>) -> Result<()> {
let current = self.current.to_owned();
pub fn update(&self, new_hotkeys: Vec<String>) -> Result<()> {
let mut current = self.current.lock();
let old_map = Self::get_map_from_vec(&current);
let new_map = Self::get_map_from_vec(&new_hotkeys);
@ -91,10 +97,10 @@ impl Hotkey {
});
add.iter().for_each(|(key, func)| {
log_if_err!(self.register(key, func));
log_err!(self.register(key, func));
});
self.current = new_hotkeys;
*current = new_hotkeys;
Ok(())
}

View File

@ -0,0 +1,36 @@
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use std::{collections::VecDeque, sync::Arc};
const LOGS_QUEUE_LEN: usize = 100;
pub struct Logger {
log_data: Arc<Mutex<VecDeque<String>>>,
}
impl Logger {
pub fn global() -> &'static Logger {
static LOGGER: OnceCell<Logger> = OnceCell::new();
LOGGER.get_or_init(|| Logger {
log_data: Arc::new(Mutex::new(VecDeque::with_capacity(LOGS_QUEUE_LEN + 10))),
})
}
pub fn get_log(&self) -> VecDeque<String> {
self.log_data.lock().clone()
}
pub fn set_log(&self, text: String) {
let mut logs = self.log_data.lock();
if logs.len() > LOGS_QUEUE_LEN {
logs.pop_front();
}
logs.push_back(text);
}
pub fn clear_log(&self) {
let mut logs = self.log_data.lock();
logs.clear();
}
}

View File

@ -1,349 +1,347 @@
use self::handle::Handle;
use self::hotkey::Hotkey;
use self::sysopt::Sysopt;
use self::timer::Timer;
use crate::config::enhance_config;
use crate::data::*;
use crate::log_if_err;
use anyhow::{bail, Result};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use serde_yaml::{Mapping, Value};
use std::sync::Arc;
// use self::handle::Handle;
// use self::hotkey::Hotkey;
// use self::sysopt::Sysopt;
// use self::timer::Timer;
// // use crate::data::*;
// // use crate::enhance::enhance_config;
// use crate::log_if_err;
// use anyhow::{bail, Result};
// use once_cell::sync::OnceCell;
// use parking_lot::Mutex;
// use serde_yaml::{Mapping, Value};
// use std::sync::Arc;
mod handle;
mod hotkey;
mod service;
mod sysopt;
mod timer;
pub mod clash_api;
mod core;
pub mod core_service;
pub mod handle;
pub mod hotkey;
pub mod logger;
// pub mod service;
pub mod sysopt;
pub mod timer;
pub mod tray;
pub use self::service::*;
pub use self::core::*;
// pub use self::service::*;
#[derive(Clone)]
pub struct Core {
pub service: Arc<Mutex<Service>>,
pub sysopt: Arc<Mutex<Sysopt>>,
pub timer: Arc<Mutex<Timer>>,
pub hotkey: Arc<Mutex<Hotkey>>,
pub runtime: Arc<Mutex<RuntimeResult>>,
pub handle: Arc<Mutex<Handle>>,
}
pub struct Core {}
impl Core {
pub fn global() -> &'static Core {
static CORE: OnceCell<Core> = OnceCell::new();
// pub fn global() -> &'static Core {
// static CORE: OnceCell<Core> = OnceCell::new();
CORE.get_or_init(|| Core {
service: Arc::new(Mutex::new(Service::new())),
sysopt: Arc::new(Mutex::new(Sysopt::new())),
timer: Arc::new(Mutex::new(Timer::new())),
hotkey: Arc::new(Mutex::new(Hotkey::new())),
runtime: Arc::new(Mutex::new(RuntimeResult::default())),
handle: Arc::new(Mutex::new(Handle::default())),
})
}
// CORE.get_or_init(|| Core {
// service: Arc::new(Mutex::new(Service::new())),
// sysopt: Arc::new(Mutex::new(Sysopt::new())),
// timer: Arc::new(Mutex::new(Timer::new())),
// hotkey: Arc::new(Mutex::new(Hotkey::new())),
// runtime: Arc::new(Mutex::new(RuntimeResult::default())),
// handle: Arc::new(Mutex::new(Handle::default())),
// })
// }
/// initialize the core state
pub fn init(&self, app_handle: tauri::AppHandle) {
// /// initialize the core state
// pub fn init(&self, app_handle: tauri::AppHandle) {
// kill old clash process
Service::kill_old_clash();
// Service::kill_old_clash();
let mut handle = self.handle.lock();
handle.set_inner(app_handle.clone());
drop(handle);
// 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);
// let mut service = self.service.lock();
// log_if_err!(service.start());
// drop(service);
log_if_err!(self.activate());
// 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 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 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);
// 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());
}
// // 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()
}
// /// 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());
// /// 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}\"");
}
// if &clash_core != "clash" && &clash_core != "clash-meta" {
// bail!("invalid clash core name \"{clash_core}\"");
// }
let global = Data::global();
let mut verge = global.verge.lock();
verge.patch_config(Verge {
clash_core: Some(clash_core.clone()),
..Verge::default()
})?;
drop(verge);
// let global = Data::global();
// let mut verge = global.verge.lock();
// verge.patch_config(Verge {
// clash_core: Some(clash_core.clone()),
// ..Verge::default()
// })?;
// drop(verge);
let mut service = self.service.lock();
service.clear_logs();
service.restart()?;
drop(service);
// let mut service = self.service.lock();
// service.clear_logs();
// service.restart()?;
// drop(service);
self.activate()
}
// self.activate()
// }
/// Patch Clash
/// handle the clash config changed
pub fn patch_clash(&self, patch: Mapping) -> Result<()> {
let patch_cloned = patch.clone();
let clash_mode = patch.get("mode");
let mixed_port = patch.get("mixed-port");
let external = patch.get("external-controller");
let secret = patch.get("secret");
// /// Patch Clash
// /// handle the clash config changed
// pub fn patch_clash(&self, patch: Mapping) -> Result<()> {
// let patch_cloned = patch.clone();
// let clash_mode = patch.get("mode");
// let mixed_port = patch.get("mixed-port");
// let external = patch.get("external-controller");
// let secret = patch.get("secret");
let valid_port = {
let global = Data::global();
let mut clash = global.clash.lock();
clash.patch_config(patch_cloned)?;
clash.info.port.is_some()
};
// let valid_port = {
// let global = Data::global();
// let mut clash = global.clash.lock();
// clash.patch_config(patch_cloned)?;
// clash.info.port.is_some()
// };
// todo: port check
if (mixed_port.is_some() && valid_port) || external.is_some() || secret.is_some() {
let mut service = self.service.lock();
service.restart()?;
drop(service);
// // todo: port check
// if (mixed_port.is_some() && valid_port) || external.is_some() || secret.is_some() {
// let mut service = self.service.lock();
// service.restart()?;
// drop(service);
self.activate()?;
// self.activate()?;
let mut sysopt = self.sysopt.lock();
sysopt.init_sysproxy()?;
}
// let mut sysopt = self.sysopt.lock();
// sysopt.init_sysproxy()?;
// }
if clash_mode.is_some() {
let handle = self.handle.lock();
handle.update_systray_part()?;
}
// if clash_mode.is_some() {
// let handle = self.handle.lock();
// handle.update_systray_part()?;
// }
Ok(())
}
// 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);
// /// 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;
// 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;
// #[cfg(target_os = "windows")]
// {
// let service_mode = patch.enable_service_mode;
// 重启服务
if service_mode.is_some() {
let mut service = self.service.lock();
service.restart()?;
drop(service);
}
// // 重启服务
// if 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 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()?;
}
}
// if service_mode.is_some() || tun_mode.is_some() {
// self.activate()?;
// }
// }
#[cfg(not(target_os = "windows"))]
if tun_mode.is_some() {
self.activate()?;
}
// #[cfg(not(target_os = "windows"))]
// if tun_mode.is_some() {
// self.activate()?;
// }
let mut sysopt = self.sysopt.lock();
// 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();
}
// 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()?;
}
// // 更新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())?;
}
// if patch.hotkeys.is_some() {
// let mut hotkey = self.hotkey.lock();
// hotkey.update(patch.hotkeys.unwrap())?;
// }
Ok(())
}
// 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()
};
// /// 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 mut mapping = Mapping::new();
// mapping.insert(Value::from("mode"), Value::from(mode));
let handle = self.handle.clone();
// let handle = self.handle.clone();
tauri::async_runtime::spawn(async move {
log_if_err!(Service::patch_config(info, mapping.to_owned()).await);
// 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());
});
// // update tray
// let handle = handle.lock();
// handle.refresh_clash();
// log_if_err!(handle.update_systray_part());
// });
Ok(())
}
// Ok(())
// }
/// activate the profile
/// auto activate enhanced profile
/// 触发clash配置更新
pub fn activate(&self) -> Result<()> {
let global = Data::global();
// /// 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 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 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();
// let clash_config = clash.config.clone();
// let clash_info = clash.info.clone();
drop(clash);
drop(verge);
drop(profiles);
// 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 (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 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 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}")
}
}
});
// 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(())
}
// Ok(())
// }
/// Static function
/// update profile item
pub async fn update_profile_item(&self, uid: String, option: Option<PrfOption>) -> Result<()> {
let global = Data::global();
// /// 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)?;
// 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())
};
// 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 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)?;
// 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()?;
}
// // reactivate the profile
// if Some(uid) == profiles.get_current() {
// drop(profiles);
// self.activate()?;
// }
Ok(())
}
// Ok(())
// }
}

View File

@ -213,7 +213,7 @@ impl Service {
/// update clash config
/// using PUT methods
pub async fn set_config(info: ClashInfo, config: Mapping) -> Result<()> {
let temp_path = dirs::profiles_temp_path();
let temp_path = dirs::clash_runtime_yaml();
config::save_yaml(temp_path.clone(), &config, Some("# Clash Verge Temp File"))?;
let (server, headers) = Self::clash_client_info(info)?;

View File

@ -1,23 +1,25 @@
use crate::{data::*, log_if_err};
use crate::{config, log_err};
use anyhow::{anyhow, bail, Result};
use auto_launch::{AutoLaunch, AutoLaunchBuilder};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use std::sync::Arc;
use sysproxy::Sysproxy;
use tauri::{async_runtime::Mutex, utils::platform::current_exe};
use tauri::{async_runtime::Mutex as TokioMutex, utils::platform::current_exe};
pub struct Sysopt {
/// current system proxy setting
cur_sysproxy: Option<Sysproxy>,
cur_sysproxy: Arc<Mutex<Option<Sysproxy>>>,
/// record the original system proxy
/// recover it when exit
old_sysproxy: Option<Sysproxy>,
old_sysproxy: Arc<Mutex<Option<Sysproxy>>>,
/// helps to auto launch the app
auto_launch: Option<AutoLaunch>,
auto_launch: Arc<Mutex<Option<AutoLaunch>>>,
/// record whether the guard async is running or not
guard_state: Arc<Mutex<bool>>,
guard_state: Arc<TokioMutex<bool>>,
}
#[cfg(target_os = "windows")]
@ -28,44 +30,45 @@ static DEFAULT_BYPASS: &str = "localhost,127.0.0.1/8,::1";
static DEFAULT_BYPASS: &str = "127.0.0.1,localhost,<local>";
impl Sysopt {
pub fn new() -> Sysopt {
Sysopt {
cur_sysproxy: None,
old_sysproxy: None,
auto_launch: None,
guard_state: Arc::new(Mutex::new(false)),
}
pub fn global() -> &'static Sysopt {
static SYSOPT: OnceCell<Sysopt> = OnceCell::new();
SYSOPT.get_or_init(|| Sysopt {
cur_sysproxy: Arc::new(Mutex::new(None)),
old_sysproxy: Arc::new(Mutex::new(None)),
auto_launch: Arc::new(Mutex::new(None)),
guard_state: Arc::new(TokioMutex::new(false)),
})
}
/// init the sysproxy
pub fn init_sysproxy(&mut self) -> Result<()> {
let data = Data::global();
let clash = data.clash.lock();
let port = clash.info.port.clone();
pub fn init_sysproxy(&self) -> Result<()> {
let port = { config::ClashN::global().info.lock().port.clone() };
if port.is_none() {
bail!("clash port is none");
}
let verge = data.verge.lock();
let port = port.unwrap().parse::<u16>()?;
let verge = config::VergeN::global().config.lock();
let enable = verge.enable_system_proxy.clone().unwrap_or(false);
let bypass = verge.system_proxy_bypass.clone();
let bypass = bypass.unwrap_or(DEFAULT_BYPASS.into());
let port = port.unwrap().parse::<u16>()?;
let host = String::from("127.0.0.1");
self.cur_sysproxy = Some(Sysproxy {
let current = Sysproxy {
enable,
host,
host: String::from("127.0.0.1"),
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()?;
let old = Sysproxy::get_system_proxy().map_or(None, |p| Some(p));
current.set_system_proxy()?;
*self.old_sysproxy.lock() = old;
*self.cur_sysproxy.lock() = Some(current);
}
// run the system proxy guard
@ -74,37 +77,44 @@ impl Sysopt {
}
/// update the system proxy
pub fn update_sysproxy(&mut self) -> Result<()> {
if self.cur_sysproxy.is_none() || self.old_sysproxy.is_none() {
pub fn update_sysproxy(&self) -> Result<()> {
let mut cur_sysproxy = self.cur_sysproxy.lock();
let old_sysproxy = self.old_sysproxy.lock();
if cur_sysproxy.is_none() || old_sysproxy.is_none() {
drop(cur_sysproxy);
drop(old_sysproxy);
return self.init_sysproxy();
}
let data = Data::global();
let verge = data.verge.lock();
let verge = config::VergeN::global().config.lock();
let enable = verge.enable_system_proxy.clone().unwrap_or(false);
let bypass = verge.system_proxy_bypass.clone();
let bypass = bypass.unwrap_or(DEFAULT_BYPASS.into());
let mut sysproxy = self.cur_sysproxy.take().unwrap();
let mut sysproxy = cur_sysproxy.take().unwrap();
sysproxy.enable = enable;
sysproxy.bypass = bypass;
self.cur_sysproxy = Some(sysproxy);
self.cur_sysproxy.as_ref().unwrap().set_system_proxy()?;
sysproxy.set_system_proxy()?;
*cur_sysproxy = Some(sysproxy);
Ok(())
}
/// reset the sysproxy
pub fn reset_sysproxy(&mut self) -> Result<()> {
let cur = self.cur_sysproxy.take();
pub fn reset_sysproxy(&self) -> Result<()> {
let mut cur_sysproxy = self.cur_sysproxy.lock();
let mut old_sysproxy = self.old_sysproxy.lock();
if let Some(mut old) = self.old_sysproxy.take() {
let cur_sysproxy = cur_sysproxy.take();
if let Some(mut old) = old_sysproxy.take() {
// 如果原代理和当前代理 端口一致就disable关闭否则就恢复原代理设置
// 当前没有设置代理的时候,不确定旧设置是否和当前一致,全关了
let port_same = cur.map_or(true, |cur| old.port == cur.port);
let port_same = cur_sysproxy.map_or(true, |cur| old.port == cur.port);
if old.enable && port_same {
old.enable = false;
@ -114,7 +124,7 @@ impl Sysopt {
}
old.set_system_proxy()?;
} else if let Some(mut cur @ Sysproxy { enable: true, .. }) = cur {
} else if let Some(mut cur @ Sysproxy { enable: true, .. }) = cur_sysproxy {
// 没有原代理就按现在的代理设置disable即可
log::info!(target: "app", "reset proxy by disabling the current proxy");
cur.enable = false;
@ -127,9 +137,8 @@ impl Sysopt {
}
/// init the auto launch
pub fn init_launch(&mut self) -> Result<()> {
let data = Data::global();
let verge = data.verge.lock();
pub fn init_launch(&self) -> Result<()> {
let verge = config::VergeN::global().config.lock();
let enable = verge.enable_auto_launch.clone().unwrap_or(false);
let app_exe = current_exe()?;
@ -167,51 +176,41 @@ impl Sysopt {
.set_app_path(&app_path)
.build()?;
self.auto_launch = Some(auto);
// 避免在开发时将自启动关了
#[cfg(feature = "verge-dev")]
if !enable {
return Ok(());
}
let auto = self.auto_launch.as_ref().unwrap();
// macos每次启动都更新登录项避免重复设置登录项
#[cfg(target_os = "macos")]
{
let _ = auto.disable();
if enable {
auto.enable()?;
}
}
#[cfg(not(target_os = "macos"))]
{
match enable {
true => auto.enable()?,
false => auto.disable()?,
};
}
*self.auto_launch.lock() = Some(auto);
Ok(())
}
/// update the startup
pub fn update_launch(&mut self) -> Result<()> {
if self.auto_launch.is_none() {
pub fn update_launch(&self) -> Result<()> {
let auto_launch = self.auto_launch.lock();
if auto_launch.is_none() {
drop(auto_launch);
return self.init_launch();
}
let data = Data::global();
let verge = data.verge.lock();
let verge = config::VergeN::global().config.lock();
let enable = verge.enable_auto_launch.clone().unwrap_or(false);
let auto_launch = self.auto_launch.as_ref().unwrap();
let auto_launch = auto_launch.as_ref().unwrap();
match enable {
true => auto_launch.enable()?,
false => crate::log_if_err!(auto_launch.disable()), // 忽略关闭的错误
false => log_err!(auto_launch.disable()), // 忽略关闭的错误
};
Ok(())
@ -239,8 +238,7 @@ impl Sysopt {
loop {
sleep(Duration::from_secs(wait_secs)).await;
let global = Data::global();
let verge = global.verge.lock();
let verge = config::VergeN::global().config.lock();
let enable = verge.enable_system_proxy.clone().unwrap_or(false);
let guard = verge.enable_proxy_guard.clone().unwrap_or(false);
@ -256,14 +254,10 @@ impl Sysopt {
// update duration
wait_secs = guard_duration;
let clash = global.clash.lock();
let port = clash.info.port.clone();
let port = port.unwrap_or("".into()).parse::<u16>();
drop(clash);
log::debug!(target: "app", "try to guard the system proxy");
match port {
let port = { config::ClashN::global().info.lock().port.clone() };
match port.unwrap_or("".into()).parse::<u16>() {
Ok(port) => {
let sysproxy = Sysproxy {
enable: true,
@ -272,14 +266,17 @@ impl Sysopt {
bypass: bypass.unwrap_or(DEFAULT_BYPASS.into()),
};
log_if_err!(sysproxy.set_system_proxy());
log_err!(sysproxy.set_system_proxy());
}
Err(_) => {
log::error!(target: "app", "failed to parse clash port in guard proxy")
}
Err(_) => log::error!(target: "app", "failed to parse clash port"),
}
}
let mut state = guard_state.lock().await;
*state = false;
drop(state);
});
}
}

View File

@ -1,92 +1,106 @@
use super::Core;
use crate::utils::help::get_now;
use crate::{data::Data, log_if_err};
use crate::config::{self, ProfilesN};
use anyhow::{Context, Result};
use delay_timer::prelude::{DelayTimer, DelayTimerBuilder, TaskBuilder};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use std::collections::HashMap;
use std::sync::Arc;
type TaskID = u64;
pub struct Timer {
/// cron manager
delay_timer: DelayTimer,
delay_timer: Arc<Mutex<DelayTimer>>,
/// save the current state
timer_map: HashMap<String, (TaskID, u64)>,
timer_map: Arc<Mutex<HashMap<String, (TaskID, u64)>>>,
/// increment id
timer_count: TaskID,
timer_count: Arc<Mutex<TaskID>>,
}
impl Timer {
pub fn new() -> Self {
Timer {
delay_timer: DelayTimerBuilder::default().build(),
timer_map: HashMap::new(),
timer_count: 1,
}
pub fn global() -> &'static Timer {
static TIMER: OnceCell<Timer> = OnceCell::new();
TIMER.get_or_init(|| Timer {
delay_timer: Arc::new(Mutex::new(DelayTimerBuilder::default().build())),
timer_map: Arc::new(Mutex::new(HashMap::new())),
timer_count: Arc::new(Mutex::new(1)),
})
}
/// Correctly update all cron tasks
pub fn refresh(&mut self) -> Result<()> {
let diff_map = self.gen_diff();
/// restore timer
pub fn init(&self) -> Result<()> {
self.refresh()?;
for (uid, diff) in diff_map.into_iter() {
match diff {
DiffFlag::Del(tid) => {
let _ = self.timer_map.remove(&uid);
log_if_err!(self.delay_timer.remove_task(tid));
}
DiffFlag::Add(tid, val) => {
let _ = self.timer_map.insert(uid.clone(), (tid, val));
log_if_err!(self.add_task(uid, tid, val));
}
DiffFlag::Mod(tid, val) => {
let _ = self.timer_map.insert(uid.clone(), (tid, val));
log_if_err!(self.delay_timer.remove_task(tid));
log_if_err!(self.add_task(uid, tid, val));
let cur_timestamp = chrono::Local::now().timestamp();
let profiles = config::ProfilesN::global().config.lock();
let timer_map = self.timer_map.lock();
let delay_timer = self.delay_timer.lock();
profiles.get_items().map(|items| {
items
.iter()
// .filter_map(|item| {
// item.uid.is_some() && item.updated.is_some() && item.option.is_some()
// })
.filter_map(|item| {
// mins to seconds
let interval = ((item.option.as_ref()?.update_interval?) as i64) * 60;
let updated = item.updated? as i64;
if interval > 0 && cur_timestamp - updated >= interval {
Some(item)
} else {
None
}
})
.for_each(|item| {
if let Some(uid) = item.uid.as_ref() {
if let Some((task_id, _)) = timer_map.get(uid) {
crate::log_err!(delay_timer.advance_task(*task_id));
}
}
})
});
Ok(())
}
/// restore timer
pub fn restore(&mut self) -> Result<()> {
self.refresh()?;
/// Correctly update all cron tasks
pub fn refresh(&self) -> Result<()> {
let diff_map = self.gen_diff();
let cur_timestamp = get_now(); // seconds
let mut timer_map = self.timer_map.lock();
let delay_timer = self.delay_timer.lock();
let global = Data::global();
let profiles = global.profiles.lock();
profiles
.get_items()
.unwrap_or(&vec![])
.iter()
.filter(|item| item.uid.is_some() && item.updated.is_some() && item.option.is_some())
.filter(|item| {
// mins to seconds
let interval =
item.option.as_ref().unwrap().update_interval.unwrap_or(0) as usize * 60;
let updated = item.updated.unwrap();
return interval > 0 && cur_timestamp - updated >= interval;
})
.for_each(|item| {
let uid = item.uid.as_ref().unwrap();
if let Some((task_id, _)) = self.timer_map.get(uid) {
log_if_err!(self.delay_timer.advance_task(*task_id));
for (uid, diff) in diff_map.into_iter() {
match diff {
DiffFlag::Del(tid) => {
let _ = timer_map.remove(&uid);
crate::log_err!(delay_timer.remove_task(tid));
}
DiffFlag::Add(tid, val) => {
let _ = timer_map.insert(uid.clone(), (tid, val));
crate::log_err!(self.add_task(uid, tid, val));
}
DiffFlag::Mod(tid, val) => {
let _ = timer_map.insert(uid.clone(), (tid, val));
crate::log_err!(delay_timer.remove_task(tid));
crate::log_err!(self.add_task(uid, tid, val));
}
}
}
});
Ok(())
}
/// generate a uid -> update_interval map
fn gen_map(&self) -> HashMap<String, u64> {
let global = Data::global();
let profiles = global.profiles.lock();
let profiles = config::ProfilesN::global().config.lock();
let mut new_map = HashMap::new();
@ -107,11 +121,13 @@ impl Timer {
}
/// generate the diff map for refresh
fn gen_diff(&mut self) -> HashMap<String, DiffFlag> {
fn gen_diff(&self) -> HashMap<String, DiffFlag> {
let mut diff_map = HashMap::new();
let timer_map = self.timer_map.lock();
let new_map = self.gen_map();
let cur_map = &self.timer_map;
let cur_map = &timer_map;
cur_map.iter().for_each(|(uid, (tid, val))| {
let new_val = new_map.get(uid).unwrap_or(&0);
@ -123,34 +139,31 @@ impl Timer {
}
});
let mut count = self.timer_count;
let mut count = self.timer_count.lock();
new_map.iter().for_each(|(uid, val)| {
if cur_map.get(uid).is_none() {
diff_map.insert(uid.clone(), DiffFlag::Add(count, *val));
diff_map.insert(uid.clone(), DiffFlag::Add(*count, *val));
count += 1;
*count += 1;
}
});
self.timer_count = count;
diff_map
}
/// add a cron task
fn add_task(&self, uid: String, tid: TaskID, minutes: u64) -> Result<()> {
let core = Core::global();
let task = TaskBuilder::default()
.set_task_id(tid)
.set_maximum_parallel_runnable_num(1)
.set_frequency_repeated_by_minutes(minutes)
// .set_frequency_repeated_by_seconds(minutes) // for test
.spawn_async_routine(move || Self::async_task(core.to_owned(), uid.to_owned()))
.spawn_async_routine(move || Self::async_task(uid.to_owned()))
.context("failed to create timer task")?;
self.delay_timer
.lock()
.add_task(task)
.context("failed to add timer task")?;
@ -158,9 +171,9 @@ impl Timer {
}
/// the task runner
async fn async_task(core: Core, uid: String) {
async fn async_task(uid: String) {
log::info!(target: "app", "running timer task `{uid}`");
log_if_err!(core.update_profile_item(uid, None).await);
crate::log_err!(ProfilesN::global().update_item(uid, None).await);
}
}

View File

@ -1,5 +1,6 @@
use crate::{data::Data, feat, utils::resolve};
use anyhow::{Ok, Result};
use crate::log_err;
use crate::{config, feat, utils::resolve};
use anyhow::Result;
use tauri::{
api, AppHandle, CustomMenuItem, Manager, SystemTrayEvent, SystemTrayMenu, SystemTrayMenuItem,
SystemTraySubmenu,
@ -9,14 +10,16 @@ pub struct Tray {}
impl Tray {
pub fn tray_menu(app_handle: &AppHandle) -> SystemTrayMenu {
let data = Data::global();
let zh = {
let verge = data.verge.lock();
let verge = config::VergeN::global().config.lock();
verge.language == Some("zh".into())
};
let version = app_handle.package_info().version.to_string();
dbg!(&zh);
dbg!(&version);
if zh {
SystemTrayMenu::new()
.add_item(CustomMenuItem::new("open_window", "打开面板"))
@ -75,13 +78,14 @@ impl Tray {
}
pub fn update_part(app_handle: &AppHandle) -> Result<()> {
let global = Data::global();
let clash = global.clash.lock();
let mode = clash
.config
.get(&serde_yaml::Value::from("mode"))
let mode = {
let clash = config::ClashN::global().config.lock();
clash
.get("mode")
.map(|val| val.as_str().unwrap_or("rule"))
.unwrap_or("rule");
.unwrap_or("rule")
.to_owned()
};
let tray = app_handle.tray_handle();
@ -90,7 +94,7 @@ impl Tray {
let _ = tray.get_item("direct_mode").set_selected(mode == "direct");
let _ = tray.get_item("script_mode").set_selected(mode == "script");
let verge = global.verge.lock();
let verge = config::VergeN::global().config.lock();
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
@ -105,12 +109,12 @@ impl Tray {
SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() {
mode @ ("rule_mode" | "global_mode" | "direct_mode" | "script_mode") => {
let mode = &mode[0..mode.len() - 5];
feat::change_clash_mode(mode);
feat::change_clash_mode(mode.into());
}
"open_window" => resolve::create_window(app_handle),
"system_proxy" => feat::toggle_system_proxy(),
"tun_mode" => feat::toggle_tun_mode(),
"system_proxy" => log_err!(feat::toggle_system_proxy()),
"tun_mode" => log_err!(feat::toggle_tun_mode()),
"restart_clash" => feat::restart_clash_core(),
"restart_app" => api::process::restart(&app_handle.env()),
"quit" => {
@ -124,7 +128,9 @@ impl Tray {
SystemTrayEvent::LeftClick { .. } => {
resolve::create_window(app_handle);
}
_ => {}
e @ _ => {
dbg!("trya");
}
}
}
}

View File

@ -0,0 +1,68 @@
mod field;
mod merge;
mod script;
mod tun;
pub(self) use self::field::*;
use self::merge::*;
use self::script::*;
use self::tun::*;
use crate::config::{ChainItem, ChainType};
use serde_yaml::Mapping;
use std::collections::HashMap;
use std::collections::HashSet;
type ResultLog = Vec<(String, String)>;
pub fn enhance_config(
clash_config: Mapping,
profile_config: Mapping,
chain: Vec<ChainItem>,
valid: Vec<String>,
tun_mode: bool,
) -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
let mut config = profile_config;
let mut result_map = HashMap::new();
let mut exists_keys = use_keys(&config);
let valid = use_valid_fields(valid);
chain.into_iter().for_each(|item| match item.data {
ChainType::Merge(merge) => {
exists_keys.extend(use_keys(&merge));
config = use_merge(merge, config.to_owned());
config = use_filter(config.to_owned(), &valid);
}
ChainType::Script(script) => {
let mut logs = vec![];
match use_script(script, config.to_owned()) {
Ok((res_config, res_logs)) => {
exists_keys.extend(use_keys(&res_config));
config = use_filter(res_config, &valid);
logs.extend(res_logs);
}
Err(err) => logs.push(("exception".into(), err.to_string())),
}
result_map.insert(item.uid, logs);
}
});
config = use_filter(config, &valid);
for (key, value) in clash_config.into_iter() {
config.insert(key, value);
}
let clash_fields = use_clash_fields();
config = use_filter(config, &clash_fields);
config = use_tun(config, tun_mode);
config = use_sort(config);
let mut exists_set = HashSet::new();
exists_set.extend(exists_keys.into_iter().filter(|s| clash_fields.contains(s)));
exists_keys = exists_set.into_iter().collect();
(config, exists_keys, result_map)
}

View File

@ -1,102 +1,214 @@
use crate::config::*;
use crate::core::*;
use crate::data::*;
use crate::log_if_err;
use crate::log_err;
use anyhow::Result;
use serde_yaml::{Mapping, Value};
// 重启clash
pub fn restart_clash_core() {
let core = Core::global();
let mut service = core.service.lock();
log_if_err!(service.restart());
drop(service);
log_if_err!(core.activate());
tauri::async_runtime::spawn(async {
CoreManager::global().run_core()?;
log_err!(handle_activate().await);
<Result<()>>::Ok(())
});
}
// 切换模式
pub fn change_clash_mode(mode: &str) {
let core = Core::global();
log_if_err!(core.update_mode(mode));
// 切换模式 rule/global/direct/script mode
pub fn change_clash_mode(mode: String) {
let mut mapping = Mapping::new();
mapping.insert(Value::from("mode"), mode.clone().into());
tauri::async_runtime::spawn(async move {
match clash_api::patch_configs(&mapping).await {
Ok(_) => {
// 更新配置
let mut clash = ClashN::global().config.lock();
clash.insert(Value::from("mode"), mode.into());
drop(clash);
if let Ok(_) = ClashN::global().save_config() {
handle::Handle::refresh_clash();
log_err!(handle::Handle::update_systray_part());
}
}
Err(err) => {
log::error!(target: "app", "{err}");
}
}
});
}
// 切换系统代理
pub fn toggle_system_proxy() {
let core = Core::global();
let data = Data::global();
let verge = data.verge.lock();
let enable = !verge.enable_system_proxy.clone().unwrap_or(false);
drop(verge);
log_if_err!(core.patch_verge(Verge {
enable_system_proxy: Some(enable),
..Verge::default()
}));
let handle = core.handle.lock();
let _ = handle.refresh_verge();
pub fn toggle_system_proxy() -> Result<()> {
let enable = {
let verge = VergeN::global().config.lock();
verge.enable_system_proxy.clone().unwrap_or(false)
};
patch_verge(IVerge {
enable_system_proxy: Some(!enable),
..IVerge::default()
})?;
handle::Handle::refresh_verge();
Ok(())
}
// 打开系统代理
pub fn enable_system_proxy() {
let core = Core::global();
log_if_err!(core.patch_verge(Verge {
pub fn enable_system_proxy() -> Result<()> {
patch_verge(IVerge {
enable_system_proxy: Some(true),
..Verge::default()
}));
let handle = core.handle.lock();
let _ = handle.refresh_verge();
..IVerge::default()
})?;
handle::Handle::refresh_verge();
Ok(())
}
// 关闭系统代理
pub fn disable_system_proxy() {
let core = Core::global();
log_if_err!(core.patch_verge(Verge {
pub fn disable_system_proxy() -> Result<()> {
patch_verge(IVerge {
enable_system_proxy: Some(false),
..Verge::default()
}));
let handle = core.handle.lock();
let _ = handle.refresh_verge();
..IVerge::default()
})?;
handle::Handle::refresh_verge();
Ok(())
}
// 切换tun模式
pub fn toggle_tun_mode() {
let core = Core::global();
let data = Data::global();
pub fn toggle_tun_mode() -> Result<()> {
let enable = {
let verge = VergeN::global().config.lock();
verge.enable_tun_mode.clone().unwrap_or(false)
};
let verge = data.verge.lock();
let enable = !verge.enable_tun_mode.clone().unwrap_or(false);
drop(verge);
log_if_err!(core.patch_verge(Verge {
enable_tun_mode: Some(enable),
..Verge::default()
}));
let handle = core.handle.lock();
let _ = handle.refresh_verge();
patch_verge(IVerge {
enable_tun_mode: Some(!enable),
..IVerge::default()
})?;
handle::Handle::refresh_verge();
Ok(())
}
// 打开tun模式
pub fn enable_tun_mode() {
let core = Core::global();
log_if_err!(core.patch_verge(Verge {
pub fn enable_tun_mode() -> Result<()> {
patch_verge(IVerge {
enable_tun_mode: Some(true),
..Verge::default()
}));
let handle = core.handle.lock();
let _ = handle.refresh_verge();
..IVerge::default()
})?;
handle::Handle::refresh_verge();
Ok(())
}
// 关闭tun模式
pub fn disable_tun_mode() {
let core = Core::global();
log_if_err!(core.patch_verge(Verge {
pub fn disable_tun_mode() -> Result<()> {
patch_verge(IVerge {
enable_tun_mode: Some(false),
..Verge::default()
}));
let handle = core.handle.lock();
let _ = handle.refresh_verge();
..IVerge::default()
})?;
handle::Handle::refresh_verge();
Ok(())
}
/// 修改clash的配置
pub fn patch_clash(patch: Mapping) -> Result<()> {
let patch_cloned = patch.clone();
let clash_mode = patch.get("mode").is_some();
let mixed_port = patch.get("mixed-port").is_some();
let external = patch.get("external-controller").is_some();
let secret = patch.get("secret").is_some();
// 更新info信息
if mixed_port || external || secret {
let mut tmp_config = { ClashN::global().config.lock().clone() };
for (key, value) in patch.into_iter() {
tmp_config.insert(key, value);
}
let old_info = ClashN::global().patch_info(ClashInfoN::from(&tmp_config))?;
if let Err(err) = CoreManager::global().run_core() {
// 恢复旧值
ClashN::global().patch_info(old_info)?;
return Err(err);
}
}
// 存好再搞
ClashN::global().patch_config(patch_cloned)?;
// 激活配置
tauri::async_runtime::spawn(async move {
match handle_activate().await {
Ok(_) => {
// 更新系统代理
if mixed_port {
log_err!(sysopt::Sysopt::global().init_sysproxy());
}
if clash_mode {
log_err!(handle::Handle::update_systray_part());
}
}
Err(err) => log::error!(target: "app", "{err}"),
}
});
Ok(())
}
/// 修改verge的配置
/// 一般都是一个个的修改
pub fn patch_verge(patch: IVerge) -> Result<()> {
VergeN::global().patch_config(patch.clone())?;
let tun_mode = patch.enable_tun_mode;
let auto_launch = patch.enable_auto_launch;
let system_proxy = patch.enable_system_proxy;
let proxy_bypass = patch.system_proxy_bypass;
let proxy_guard = patch.enable_proxy_guard;
let language = patch.language;
#[cfg(target_os = "windows")]
{}
if tun_mode.is_some() {
tauri::async_runtime::spawn(async {
log_err!(handle_activate().await);
});
}
if auto_launch.is_some() {
sysopt::Sysopt::global().update_launch()?;
}
if system_proxy.is_some() || proxy_bypass.is_some() {
sysopt::Sysopt::global().update_sysproxy()?;
sysopt::Sysopt::global().guard_proxy();
}
if proxy_guard.unwrap_or(false) {
sysopt::Sysopt::global().guard_proxy();
}
if language.is_some() {
handle::Handle::update_systray()?;
} else if system_proxy.or(tun_mode).is_some() {
handle::Handle::update_systray_part()?;
}
if patch.hotkeys.is_some() {
hotkey::Hotkey::global().update(patch.hotkeys.unwrap())?;
}
Ok(())
}
/// 激活配置
pub async fn handle_activate() -> Result<()> {
match CoreManager::global().activate_config().await {
Ok(_) => {
handle::Handle::refresh_clash();
handle::Handle::notice_message("set_config::ok", "ok");
Ok(())
}
Err(err) => {
handle::Handle::notice_message("set_config::error", format!("{err}"));
Err(err)
}
}
}

View File

@ -6,12 +6,13 @@
mod cmds;
mod config;
mod core;
mod data;
// mod data;
mod enhance;
mod feat;
mod utils;
use crate::utils::{init, resolve, server};
use tauri::{api, Manager, SystemTray};
use tauri::{api, CustomMenuItem, Manager, SystemTray, SystemTrayMenu};
fn main() -> std::io::Result<()> {
// 单例检测
@ -30,8 +31,8 @@ fn main() -> std::io::Result<()> {
#[allow(unused_mut)]
let mut builder = tauri::Builder::default()
.setup(|app| Ok(resolve::resolve_setup(app)))
.system_tray(SystemTray::new())
.setup(|app| Ok(resolve::resolve_setup(app)))
.on_system_tray_event(core::tray::Tray::on_system_tray_event)
.invoke_handler(tauri::generate_handler![
// common
@ -39,7 +40,7 @@ fn main() -> std::io::Result<()> {
cmds::open_app_dir,
cmds::open_logs_dir,
cmds::open_web_url,
cmds::kill_sidecar,
// cmds::kill_sidecar,
cmds::restart_sidecar,
// clash
cmds::get_clash_info,
@ -53,12 +54,12 @@ fn main() -> std::io::Result<()> {
// verge
cmds::get_verge_config,
cmds::patch_verge_config,
cmds::update_hotkeys,
// cmds::update_hotkeys,
// profile
cmds::view_profile,
cmds::patch_profile,
cmds::create_profile,
cmds::import_profile,
// cmds::import_profile,
cmds::update_profile,
cmds::delete_profile,
cmds::select_profile,
@ -96,22 +97,17 @@ fn main() -> std::io::Result<()> {
);
}
#[allow(unused_mut)]
let mut app = builder
let app = builder
.build(tauri::generate_context!())
.expect("error while running tauri application");
#[cfg(target_os = "macos")]
app.set_activation_policy(tauri::ActivationPolicy::Accessory);
// let app_handle = app.app_handle();
// ctrlc::set_handler(move || {
// resolve::resolve_reset();
// app_handle.exit(0);
// })
// .expect("error while exiting.");
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();

View File

@ -50,7 +50,7 @@ pub fn save_yaml<T: Serialize>(path: PathBuf, data: &T, prefix: Option<&str>) ->
let data_str = serde_yaml::to_string(data)?;
let yaml_str = match prefix {
Some(prefix) => format!("{prefix}{data_str}"),
Some(prefix) => format!("{prefix}\n{data_str}"),
None => data_str,
};

View File

@ -1,4 +1,3 @@
use std::env::temp_dir;
use std::path::PathBuf;
use tauri::{
api::path::{home_dir, resource_dir},
@ -13,7 +12,7 @@ static APP_DIR: &str = "clash-verge-dev";
static CLASH_CONFIG: &str = "config.yaml";
static VERGE_CONFIG: &str = "verge.yaml";
static PROFILE_YAML: &str = "profiles.yaml";
static PROFILE_TEMP: &str = "clash-verge-runtime.yaml";
static CLASH_RUNTIME_YAML: &str = "clash-verge-runtime.yaml";
static mut RESOURCE_DIR: Option<PathBuf> = None;
@ -99,12 +98,8 @@ pub fn profiles_path() -> PathBuf {
app_home_dir().join(PROFILE_YAML)
}
pub fn profiles_temp_path() -> PathBuf {
#[cfg(not(feature = "debug-yml"))]
return temp_dir().join(PROFILE_TEMP);
#[cfg(feature = "debug-yml")]
return app_home_dir().join(PROFILE_TEMP);
pub fn clash_runtime_yaml() -> PathBuf {
app_home_dir().join(CLASH_RUNTIME_YAML)
}
pub fn clash_pid_path() -> PathBuf {
@ -136,3 +131,11 @@ pub fn service_log_file() -> PathBuf {
log_file
}
pub fn path_to_str(path: &PathBuf) -> anyhow::Result<&str> {
let path_str = path
.as_os_str()
.to_str()
.ok_or(anyhow::anyhow!("failed to get path from {:?}", path))?;
Ok(path_str)
}

View File

@ -69,6 +69,13 @@ pub fn open_file(path: PathBuf) -> Result<()> {
Ok(())
}
#[macro_export]
macro_rules! error {
($result: expr) => {
log::error!(target: "app", "{}", $result);
};
}
#[macro_export]
macro_rules! log_if_err {
($result: expr) => {
@ -78,6 +85,21 @@ macro_rules! log_if_err {
};
}
#[macro_export]
macro_rules! log_err {
($result: expr) => {
if let Err(err) = $result {
log::error!(target: "app", "{err}");
}
};
($result: expr, $err_str: expr) => {
if let Err(_) = $result {
log::error!(target: "app", "{}", $err_str);
}
};
}
/// wrap the anyhow error
/// transform the error to String
#[macro_export]

View File

@ -87,19 +87,28 @@ pub fn init_resources(package_info: &PackageInfo) {
}
// copy the resource file
let mmdb_path = app_dir.join("Country.mmdb");
let mmdb_tmpl = res_dir.join("Country.mmdb");
if !mmdb_path.exists() && mmdb_tmpl.exists() {
let _ = fs::copy(mmdb_tmpl, mmdb_path);
for file in ["Country.mmdb", "geoip.dat", "geosite.dat", "wintun.dll"].iter() {
let src_path = res_dir.join(file);
let target_path = app_dir.join(file);
if src_path.exists() {
let _ = fs::copy(src_path, target_path);
}
}
// copy the wintun.dll
#[cfg(target_os = "windows")]
{
let wintun_path = app_dir.join("wintun.dll");
let wintun_tmpl = res_dir.join("wintun.dll");
if !wintun_path.exists() && wintun_tmpl.exists() {
let _ = fs::copy(wintun_tmpl, wintun_path);
}
}
// // copy the resource file
// let mmdb_path = app_dir.join("Country.mmdb");
// let mmdb_tmpl = res_dir.join("Country.mmdb");
// if !mmdb_path.exists() && mmdb_tmpl.exists() {
// let _ = fs::copy(mmdb_tmpl, mmdb_path);
// }
// // copy the wintun.dll
// #[cfg(target_os = "windows")]
// {
// let wintun_path = app_dir.join("wintun.dll");
// let wintun_tmpl = res_dir.join("wintun.dll");
// if !wintun_path.exists() && wintun_tmpl.exists() {
// let _ = fs::copy(wintun_tmpl, wintun_path);
// }
// }
}

View File

@ -1,48 +1,48 @@
use crate::{
core::{tray, Core},
data::Data,
utils::init,
utils::server,
};
use crate::log_err;
use crate::{config::VergeN, core::*, utils::init, utils::server};
use tauri::{App, AppHandle, Manager};
/// handle something when start app
pub fn resolve_setup(app: &App) {
let _ = app
.tray_handle()
.set_menu(tray::Tray::tray_menu(&app.app_handle()));
pub fn resolve_setup(app: &mut App) {
#[cfg(target_os = "macos")]
app.set_activation_policy(tauri::ActivationPolicy::Accessory);
handle::Handle::global().init(app.app_handle());
init::init_resources(app.package_info());
let silent_start = {
let global = Data::global();
let verge = global.verge.lock();
let singleton = verge.app_singleton_port.clone();
// 启动核心
log_err!(CoreManager::global().init());
// log_err!(app
// .tray_handle()
// .set_menu(tray::Tray::tray_menu(&app.app_handle())));
log_err!(tray::Tray::update_systray(&app.app_handle()));
// setup a simple http server for singleton
server::embed_server(&app.app_handle(), singleton);
server::embed_server(app.app_handle());
verge.enable_silent_start.clone().unwrap_or(false)
let silent_start = {
let verge = VergeN::global().config.lock();
verge.enable_silent_start.clone()
};
// core should be initialized after init_app fix #122
let core = Core::global();
core.init(app.app_handle());
if !silent_start {
if !silent_start.unwrap_or(false) {
create_window(&app.app_handle());
}
log_err!(sysopt::Sysopt::global().init_launch());
log_err!(sysopt::Sysopt::global().init_sysproxy());
log_err!(handle::Handle::update_systray_part());
log_err!(hotkey::Hotkey::global().init(app.app_handle()));
log_err!(timer::Timer::global().init());
}
/// reset system proxy
pub fn resolve_reset() {
let core = Core::global();
let mut sysopt = core.sysopt.lock();
crate::log_if_err!(sysopt.reset_sysproxy());
drop(sysopt);
let mut service = core.service.lock();
crate::log_if_err!(service.stop());
log_err!(sysopt::Sysopt::global().reset_sysproxy());
log_err!(CoreManager::global().stop_core());
}
/// create main window

View File

@ -1,20 +1,14 @@
extern crate warp;
use super::resolve;
use crate::data::Verge;
use crate::config::VergeN;
use port_scanner::local_port_available;
use tauri::AppHandle;
use warp::Filter;
#[cfg(not(feature = "verge-dev"))]
const SERVER_PORT: u16 = 33331;
#[cfg(feature = "verge-dev")]
const SERVER_PORT: u16 = 11233;
/// check whether there is already exists
pub fn check_singleton() -> Result<(), ()> {
let verge = Verge::new();
let port = verge.app_singleton_port.unwrap_or(SERVER_PORT);
let port = VergeN::get_singleton_port();
if !local_port_available(port) {
tauri::async_runtime::block_on(async {
@ -29,14 +23,14 @@ pub fn check_singleton() -> Result<(), ()> {
/// The embed server only be used to implement singleton process
/// maybe it can be used as pac server later
pub fn embed_server(app_handle: &AppHandle, port: Option<u16>) {
pub fn embed_server(app_handle: AppHandle) {
let app_handle = app_handle.clone();
let port = port.unwrap_or(SERVER_PORT);
let port = VergeN::get_singleton_port();
tauri::async_runtime::spawn(async move {
let commands = warp::path!("commands" / "visible").map(move || {
resolve::create_window(&app_handle);
return format!("ok");
format!("ok")
});
warp::serve(commands).bind(([127, 0, 0, 1], port)).await;

View File

@ -136,9 +136,9 @@ export async function restartSidecar() {
return invoke<void>("restart_sidecar");
}
export async function killSidecar() {
return invoke<any>("kill_sidecar");
}
// export async function killSidecar() {
// return invoke<any>("kill_sidecar");
// }
export async function openAppDir() {
return invoke<void>("open_app_dir").catch((err) =>