feat.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  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 redir_port = patch.get("redir-port");
  99. let tproxy_port = patch.get("tproxy-port");
  100. let mixed_port = patch.get("mixed-port");
  101. let socks_port = patch.get("socks-port");
  102. let port = patch.get("port");
  103. let enable_random_port = Config::verge().latest().enable_random_port.unwrap_or(false);
  104. if mixed_port.is_some() && !enable_random_port {
  105. let changed = mixed_port.unwrap()
  106. != Config::verge()
  107. .latest()
  108. .verge_mixed_port
  109. .unwrap_or(Config::clash().data().get_mixed_port());
  110. // 检查端口占用
  111. if changed {
  112. if let Some(port) = mixed_port.unwrap().as_u64() {
  113. if !port_scanner::local_port_available(port as u16) {
  114. Config::clash().discard();
  115. bail!("port already in use");
  116. }
  117. }
  118. }
  119. };
  120. // 激活订阅
  121. if redir_port.is_some()
  122. || tproxy_port.is_some()
  123. || mixed_port.is_some()
  124. || socks_port.is_some()
  125. || port.is_some()
  126. || patch.get("secret").is_some()
  127. || patch.get("external-controller").is_some()
  128. {
  129. Config::generate()?;
  130. CoreManager::global().run_core().await?;
  131. handle::Handle::refresh_clash();
  132. }
  133. // 更新系统代理
  134. if mixed_port.is_some() {
  135. log_err!(sysopt::Sysopt::global().init_sysproxy());
  136. }
  137. if patch.get("mode").is_some() {
  138. log_err!(handle::Handle::update_systray_part());
  139. }
  140. Config::runtime().latest().patch_config(patch);
  141. <Result<()>>::Ok(())
  142. } {
  143. Ok(()) => {
  144. Config::clash().apply();
  145. Config::clash().data().save_config()?;
  146. Ok(())
  147. }
  148. Err(err) => {
  149. Config::clash().discard();
  150. Err(err)
  151. }
  152. }
  153. }
  154. /// 修改verge的订阅
  155. /// 一般都是一个个的修改
  156. pub async fn patch_verge(patch: IVerge) -> Result<()> {
  157. Config::verge().draft().patch_config(patch.clone());
  158. let tun_mode = patch.enable_tun_mode;
  159. let auto_launch = patch.enable_auto_launch;
  160. let system_proxy = patch.enable_system_proxy;
  161. let proxy_bypass = patch.system_proxy_bypass;
  162. let language = patch.language;
  163. let port = patch.verge_mixed_port;
  164. let common_tray_icon = patch.common_tray_icon;
  165. let sysproxy_tray_icon = patch.sysproxy_tray_icon;
  166. let tun_tray_icon = patch.tun_tray_icon;
  167. match {
  168. let service_mode = patch.enable_service_mode;
  169. if service_mode.is_some() {
  170. log::debug!(target: "app", "change service mode to {}", service_mode.unwrap());
  171. Config::generate()?;
  172. CoreManager::global().run_core().await?;
  173. } else if tun_mode.is_some() {
  174. update_core_config().await?;
  175. }
  176. if auto_launch.is_some() {
  177. sysopt::Sysopt::global().update_launch()?;
  178. }
  179. if system_proxy.is_some() || proxy_bypass.is_some() || port.is_some() {
  180. sysopt::Sysopt::global().update_sysproxy()?;
  181. sysopt::Sysopt::global().guard_proxy();
  182. }
  183. if let Some(true) = patch.enable_proxy_guard {
  184. sysopt::Sysopt::global().guard_proxy();
  185. }
  186. if let Some(hotkeys) = patch.hotkeys {
  187. hotkey::Hotkey::global().update(hotkeys)?;
  188. }
  189. if language.is_some() {
  190. handle::Handle::update_systray()?;
  191. } else if system_proxy.is_some()
  192. || tun_mode.is_some()
  193. || common_tray_icon.is_some()
  194. || sysproxy_tray_icon.is_some()
  195. || tun_tray_icon.is_some()
  196. {
  197. handle::Handle::update_systray_part()?;
  198. }
  199. <Result<()>>::Ok(())
  200. } {
  201. Ok(()) => {
  202. Config::verge().apply();
  203. Config::verge().data().save_file()?;
  204. Ok(())
  205. }
  206. Err(err) => {
  207. Config::verge().discard();
  208. Err(err)
  209. }
  210. }
  211. }
  212. /// 更新某个profile
  213. /// 如果更新当前订阅就激活订阅
  214. pub async fn update_profile(uid: String, option: Option<PrfOption>) -> Result<()> {
  215. let url_opt = {
  216. let profiles = Config::profiles();
  217. let profiles = profiles.latest();
  218. let item = profiles.get_item(&uid)?;
  219. let is_remote = item.itype.as_ref().map_or(false, |s| s == "remote");
  220. if !is_remote {
  221. None // 直接更新
  222. } else if item.url.is_none() {
  223. bail!("failed to get the profile item url");
  224. } else {
  225. Some((item.url.clone().unwrap(), item.option.clone()))
  226. }
  227. };
  228. let should_update = match url_opt {
  229. Some((url, opt)) => {
  230. let merged_opt = PrfOption::merge(opt, option);
  231. let item = PrfItem::from_url(&url, None, None, merged_opt).await?;
  232. let profiles = Config::profiles();
  233. let mut profiles = profiles.latest();
  234. profiles.update_item(uid.clone(), item)?;
  235. Some(uid) == profiles.get_current()
  236. }
  237. None => true,
  238. };
  239. if should_update {
  240. update_core_config().await?;
  241. }
  242. Ok(())
  243. }
  244. /// 更新订阅
  245. async fn update_core_config() -> Result<()> {
  246. match CoreManager::global().update_config().await {
  247. Ok(_) => {
  248. handle::Handle::refresh_clash();
  249. handle::Handle::notice_message("set_config::ok", "ok");
  250. Ok(())
  251. }
  252. Err(err) => {
  253. handle::Handle::notice_message("set_config::error", format!("{err}"));
  254. Err(err)
  255. }
  256. }
  257. }
  258. /// copy env variable
  259. pub fn copy_clash_env(app_handle: &AppHandle) {
  260. let port = { Config::verge().latest().verge_mixed_port.unwrap_or(7897) };
  261. let http_proxy = format!("http://127.0.0.1:{}", port);
  262. let socks5_proxy = format!("socks5://127.0.0.1:{}", port);
  263. let sh =
  264. format!("export https_proxy={http_proxy} http_proxy={http_proxy} all_proxy={socks5_proxy}");
  265. let cmd: String = format!("set http_proxy={http_proxy} \n set https_proxy={http_proxy}");
  266. let ps: String = format!("$env:HTTP_PROXY=\"{http_proxy}\"; $env:HTTPS_PROXY=\"{http_proxy}\"");
  267. let mut cliboard = app_handle.clipboard_manager();
  268. let env_type = { Config::verge().latest().env_type.clone() };
  269. let env_type = match env_type {
  270. Some(env_type) => env_type,
  271. None => {
  272. #[cfg(not(target_os = "windows"))]
  273. let default = "bash";
  274. #[cfg(target_os = "windows")]
  275. let default = "powershell";
  276. default.to_string()
  277. }
  278. };
  279. match env_type.as_str() {
  280. "bash" => cliboard.write_text(sh).unwrap_or_default(),
  281. "cmd" => cliboard.write_text(cmd).unwrap_or_default(),
  282. "powershell" => cliboard.write_text(ps).unwrap_or_default(),
  283. _ => log::error!(target: "app", "copy_clash_env: Invalid env type! {env_type}"),
  284. };
  285. }
  286. pub async fn test_delay(url: String) -> Result<u32> {
  287. use tokio::time::{Duration, Instant};
  288. let mut builder = reqwest::ClientBuilder::new().use_rustls_tls().no_proxy();
  289. let port = Config::verge()
  290. .latest()
  291. .verge_mixed_port
  292. .unwrap_or(Config::clash().data().get_mixed_port());
  293. let tun_mode = Config::verge().latest().enable_tun_mode.unwrap_or(false);
  294. let proxy_scheme = format!("http://127.0.0.1:{port}");
  295. if !tun_mode {
  296. if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) {
  297. builder = builder.proxy(proxy);
  298. }
  299. if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) {
  300. builder = builder.proxy(proxy);
  301. }
  302. if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) {
  303. builder = builder.proxy(proxy);
  304. }
  305. }
  306. let request = builder
  307. .timeout(Duration::from_millis(10000))
  308. .build()?
  309. .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");
  310. let start = Instant::now();
  311. let response = request.send().await?;
  312. if response.status().is_success() {
  313. let delay = start.elapsed().as_millis() as u32;
  314. Ok(delay)
  315. } else {
  316. Ok(10000u32)
  317. }
  318. }