feat.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. //!
  2. //! feat mod 里的函数主要用于
  3. //! - hotkey 快捷键
  4. //! - timer 定时器
  5. //! - cmds 页面调用
  6. //!
  7. use crate::config::*;
  8. use crate::core::*;
  9. use crate::log_err;
  10. use crate::utils::resolve;
  11. use anyhow::{bail, Result};
  12. use serde_yaml::{Mapping, Value};
  13. use tauri::{AppHandle, ClipboardManager, Manager};
  14. // 打开面板
  15. pub fn open_or_close_dashboard() {
  16. let handle = handle::Handle::global();
  17. let app_handle = handle.app_handle.lock();
  18. if let Some(app_handle) = app_handle.as_ref() {
  19. if let Some(window) = app_handle.get_window("main") {
  20. if let Ok(true) = window.is_focused() {
  21. let _ = window.close();
  22. return;
  23. }
  24. }
  25. resolve::create_window(app_handle);
  26. }
  27. }
  28. // 重启clash
  29. pub fn restart_clash_core() {
  30. tauri::async_runtime::spawn(async {
  31. match CoreManager::global().run_core().await {
  32. Ok(_) => {
  33. handle::Handle::refresh_clash();
  34. handle::Handle::notice_message("set_config::ok", "ok");
  35. }
  36. Err(err) => {
  37. handle::Handle::notice_message("set_config::error", format!("{err}"));
  38. log::error!(target:"app", "{err}");
  39. }
  40. }
  41. });
  42. }
  43. // 切换模式 rule/global/direct/script mode
  44. pub fn change_clash_mode(mode: String) {
  45. let mut mapping = Mapping::new();
  46. mapping.insert(Value::from("mode"), mode.clone().into());
  47. tauri::async_runtime::spawn(async move {
  48. log::debug!(target: "app", "change clash mode to {mode}");
  49. match clash_api::patch_configs(&mapping).await {
  50. Ok(_) => {
  51. // 更新订阅
  52. Config::clash().data().patch_config(mapping);
  53. if Config::clash().data().save_config().is_ok() {
  54. handle::Handle::refresh_clash();
  55. log_err!(handle::Handle::update_systray_part());
  56. }
  57. }
  58. Err(err) => log::error!(target: "app", "{err}"),
  59. }
  60. });
  61. }
  62. // 切换系统代理
  63. pub fn toggle_system_proxy() {
  64. let enable = Config::verge().draft().enable_system_proxy;
  65. let enable = enable.unwrap_or(false);
  66. tauri::async_runtime::spawn(async move {
  67. match patch_verge(IVerge {
  68. enable_system_proxy: Some(!enable),
  69. ..IVerge::default()
  70. })
  71. .await
  72. {
  73. Ok(_) => handle::Handle::refresh_verge(),
  74. Err(err) => log::error!(target: "app", "{err}"),
  75. }
  76. });
  77. }
  78. // 切换tun模式
  79. pub fn toggle_tun_mode() {
  80. let enable = Config::verge().data().enable_tun_mode;
  81. let enable = enable.unwrap_or(false);
  82. tauri::async_runtime::spawn(async move {
  83. match patch_verge(IVerge {
  84. enable_tun_mode: Some(!enable),
  85. ..IVerge::default()
  86. })
  87. .await
  88. {
  89. Ok(_) => handle::Handle::refresh_verge(),
  90. Err(err) => log::error!(target: "app", "{err}"),
  91. }
  92. });
  93. }
  94. /// 修改clash的订阅
  95. pub async fn patch_clash(patch: Mapping) -> Result<()> {
  96. Config::clash().draft().patch_config(patch.clone());
  97. match {
  98. let mixed_port = patch.get("mixed-port");
  99. let socks_port = patch.get("socks-port");
  100. let port = patch.get("port");
  101. let enable_random_port = Config::verge().latest().enable_random_port.unwrap_or(false);
  102. if mixed_port.is_some() && !enable_random_port {
  103. let changed = mixed_port.unwrap()
  104. != Config::verge()
  105. .latest()
  106. .verge_mixed_port
  107. .unwrap_or(Config::clash().data().get_mixed_port());
  108. // 检查端口占用
  109. if changed {
  110. if let Some(port) = mixed_port.unwrap().as_u64() {
  111. if !port_scanner::local_port_available(port as u16) {
  112. Config::clash().discard();
  113. bail!("port already in use");
  114. }
  115. }
  116. }
  117. };
  118. // 激活订阅
  119. if mixed_port.is_some()
  120. || socks_port.is_some()
  121. || port.is_some()
  122. || patch.get("secret").is_some()
  123. || patch.get("external-controller").is_some()
  124. {
  125. Config::generate()?;
  126. CoreManager::global().run_core().await?;
  127. handle::Handle::refresh_clash();
  128. }
  129. // 更新系统代理
  130. if mixed_port.is_some() {
  131. log_err!(sysopt::Sysopt::global().init_sysproxy());
  132. }
  133. if patch.get("mode").is_some() {
  134. log_err!(handle::Handle::update_systray_part());
  135. }
  136. Config::runtime().latest().patch_config(patch);
  137. <Result<()>>::Ok(())
  138. } {
  139. Ok(()) => {
  140. Config::clash().apply();
  141. Config::clash().data().save_config()?;
  142. Ok(())
  143. }
  144. Err(err) => {
  145. Config::clash().discard();
  146. Err(err)
  147. }
  148. }
  149. }
  150. /// 修改verge的订阅
  151. /// 一般都是一个个的修改
  152. pub async fn patch_verge(patch: IVerge) -> Result<()> {
  153. Config::verge().draft().patch_config(patch.clone());
  154. let tun_mode = patch.enable_tun_mode;
  155. let auto_launch = patch.enable_auto_launch;
  156. let system_proxy = patch.enable_system_proxy;
  157. let proxy_bypass = patch.system_proxy_bypass;
  158. let language = patch.language;
  159. let port = patch.verge_mixed_port;
  160. let common_tray_icon = patch.common_tray_icon;
  161. let sysproxy_tray_icon = patch.sysproxy_tray_icon;
  162. let tun_tray_icon = patch.tun_tray_icon;
  163. match {
  164. #[cfg(target_os = "windows")]
  165. {
  166. let service_mode = patch.enable_service_mode;
  167. if service_mode.is_some() {
  168. log::debug!(target: "app", "change service mode to {}", service_mode.unwrap());
  169. Config::generate()?;
  170. CoreManager::global().run_core().await?;
  171. } else if tun_mode.is_some() {
  172. update_core_config().await?;
  173. }
  174. }
  175. #[cfg(not(target_os = "windows"))]
  176. if tun_mode.is_some() {
  177. update_core_config().await?;
  178. }
  179. if auto_launch.is_some() {
  180. sysopt::Sysopt::global().update_launch()?;
  181. }
  182. if system_proxy.is_some() || proxy_bypass.is_some() || port.is_some() {
  183. sysopt::Sysopt::global().update_sysproxy()?;
  184. sysopt::Sysopt::global().guard_proxy();
  185. }
  186. if let Some(true) = patch.enable_proxy_guard {
  187. sysopt::Sysopt::global().guard_proxy();
  188. }
  189. if let Some(hotkeys) = patch.hotkeys {
  190. hotkey::Hotkey::global().update(hotkeys)?;
  191. }
  192. if language.is_some() {
  193. handle::Handle::update_systray()?;
  194. } else if system_proxy.is_some()
  195. || tun_mode.is_some()
  196. || common_tray_icon.is_some()
  197. || sysproxy_tray_icon.is_some()
  198. || tun_tray_icon.is_some()
  199. {
  200. handle::Handle::update_systray_part()?;
  201. }
  202. <Result<()>>::Ok(())
  203. } {
  204. Ok(()) => {
  205. Config::verge().apply();
  206. Config::verge().data().save_file()?;
  207. Ok(())
  208. }
  209. Err(err) => {
  210. Config::verge().discard();
  211. Err(err)
  212. }
  213. }
  214. }
  215. /// 更新某个profile
  216. /// 如果更新当前订阅就激活订阅
  217. pub async fn update_profile(uid: String, option: Option<PrfOption>) -> Result<()> {
  218. let url_opt = {
  219. let profiles = Config::profiles();
  220. let profiles = profiles.latest();
  221. let item = profiles.get_item(&uid)?;
  222. let is_remote = item.itype.as_ref().map_or(false, |s| s == "remote");
  223. if !is_remote {
  224. None // 直接更新
  225. } else if item.url.is_none() {
  226. bail!("failed to get the profile item url");
  227. } else {
  228. Some((item.url.clone().unwrap(), item.option.clone()))
  229. }
  230. };
  231. let should_update = match url_opt {
  232. Some((url, opt)) => {
  233. let merged_opt = PrfOption::merge(opt, option);
  234. let item = PrfItem::from_url(&url, None, None, merged_opt).await?;
  235. let profiles = Config::profiles();
  236. let mut profiles = profiles.latest();
  237. profiles.update_item(uid.clone(), item)?;
  238. Some(uid) == profiles.get_current()
  239. }
  240. None => true,
  241. };
  242. if should_update {
  243. update_core_config().await?;
  244. }
  245. Ok(())
  246. }
  247. /// 更新订阅
  248. async fn update_core_config() -> Result<()> {
  249. match CoreManager::global().update_config().await {
  250. Ok(_) => {
  251. handle::Handle::refresh_clash();
  252. handle::Handle::notice_message("set_config::ok", "ok");
  253. Ok(())
  254. }
  255. Err(err) => {
  256. handle::Handle::notice_message("set_config::error", format!("{err}"));
  257. Err(err)
  258. }
  259. }
  260. }
  261. /// copy env variable
  262. pub fn copy_clash_env(app_handle: &AppHandle) {
  263. let port = { Config::verge().latest().verge_mixed_port.unwrap_or(7897) };
  264. let http_proxy = format!("http://127.0.0.1:{}", port);
  265. let socks5_proxy = format!("socks5://127.0.0.1:{}", port);
  266. let sh =
  267. format!("export https_proxy={http_proxy} http_proxy={http_proxy} all_proxy={socks5_proxy}");
  268. let cmd: String = format!("set http_proxy={http_proxy} \n set https_proxy={http_proxy}");
  269. let ps: String = format!("$env:HTTP_PROXY=\"{http_proxy}\"; $env:HTTPS_PROXY=\"{http_proxy}\"");
  270. let mut cliboard = app_handle.clipboard_manager();
  271. let env_type = { Config::verge().latest().env_type.clone() };
  272. let env_type = match env_type {
  273. Some(env_type) => env_type,
  274. None => {
  275. #[cfg(not(target_os = "windows"))]
  276. let default = "bash";
  277. #[cfg(target_os = "windows")]
  278. let default = "powershell";
  279. default.to_string()
  280. }
  281. };
  282. match env_type.as_str() {
  283. "bash" => cliboard.write_text(sh).unwrap_or_default(),
  284. "cmd" => cliboard.write_text(cmd).unwrap_or_default(),
  285. "powershell" => cliboard.write_text(ps).unwrap_or_default(),
  286. _ => log::error!(target: "app", "copy_clash_env: Invalid env type! {env_type}"),
  287. };
  288. }
  289. pub async fn test_delay(url: String) -> Result<u32> {
  290. use tokio::time::{Duration, Instant};
  291. let mut builder = reqwest::ClientBuilder::new().use_rustls_tls().no_proxy();
  292. let port = Config::verge()
  293. .latest()
  294. .verge_mixed_port
  295. .unwrap_or(Config::clash().data().get_mixed_port());
  296. let tun_mode = Config::verge().latest().enable_tun_mode.unwrap_or(false);
  297. let proxy_scheme = format!("http://127.0.0.1:{port}");
  298. if !tun_mode {
  299. if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) {
  300. builder = builder.proxy(proxy);
  301. }
  302. if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) {
  303. builder = builder.proxy(proxy);
  304. }
  305. if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) {
  306. builder = builder.proxy(proxy);
  307. }
  308. }
  309. let request = builder
  310. .timeout(Duration::from_millis(10000))
  311. .build()?
  312. .get(url).header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0");
  313. let start = Instant::now();
  314. let response = request.send().await?;
  315. if response.status().is_success() {
  316. let delay = start.elapsed().as_millis() as u32;
  317. Ok(delay)
  318. } else {
  319. Ok(10000u32)
  320. }
  321. }