feat.rs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  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. if !enable {
  84. if let Ok(res) = service::check_service().await {
  85. if res.code == 0 {
  86. match patch_verge(IVerge {
  87. enable_tun_mode: Some(!enable),
  88. ..IVerge::default()
  89. })
  90. .await
  91. {
  92. Ok(_) => handle::Handle::refresh_verge(),
  93. Err(err) => log::error!(target: "app", "{err}"),
  94. }
  95. return;
  96. }
  97. }
  98. tauri::api::dialog::message(
  99. None::<&tauri::Window>,
  100. "Please install and enable service mode",
  101. "Service mode is required for Tun mode",
  102. );
  103. } else {
  104. match patch_verge(IVerge {
  105. enable_tun_mode: Some(!enable),
  106. ..IVerge::default()
  107. })
  108. .await
  109. {
  110. Ok(_) => handle::Handle::refresh_verge(),
  111. Err(err) => log::error!(target: "app", "{err}"),
  112. }
  113. }
  114. });
  115. }
  116. /// 修改clash的订阅
  117. pub async fn patch_clash(patch: Mapping) -> Result<()> {
  118. Config::clash().draft().patch_config(patch.clone());
  119. let res = {
  120. let redir_port = patch.get("redir-port");
  121. let tproxy_port = patch.get("tproxy-port");
  122. let mixed_port = patch.get("mixed-port");
  123. let socks_port = patch.get("socks-port");
  124. let port = patch.get("port");
  125. let enable_random_port = Config::verge().latest().enable_random_port.unwrap_or(false);
  126. if mixed_port.is_some() && !enable_random_port {
  127. let changed = mixed_port.unwrap()
  128. != Config::verge()
  129. .latest()
  130. .verge_mixed_port
  131. .unwrap_or(Config::clash().data().get_mixed_port());
  132. // 检查端口占用
  133. if changed {
  134. if let Some(port) = mixed_port.unwrap().as_u64() {
  135. if !port_scanner::local_port_available(port as u16) {
  136. Config::clash().discard();
  137. bail!("port already in use");
  138. }
  139. }
  140. }
  141. };
  142. // 激活订阅
  143. if redir_port.is_some()
  144. || tproxy_port.is_some()
  145. || mixed_port.is_some()
  146. || socks_port.is_some()
  147. || port.is_some()
  148. || patch.get("secret").is_some()
  149. || patch.get("external-controller").is_some()
  150. {
  151. Config::generate()?;
  152. CoreManager::global().run_core().await?;
  153. handle::Handle::refresh_clash();
  154. }
  155. // 更新系统代理
  156. if mixed_port.is_some() {
  157. log_err!(sysopt::Sysopt::global().init_sysproxy());
  158. }
  159. if patch.get("mode").is_some() {
  160. log_err!(handle::Handle::update_systray_part());
  161. }
  162. Config::runtime().latest().patch_config(patch);
  163. <Result<()>>::Ok(())
  164. };
  165. match res {
  166. Ok(()) => {
  167. Config::clash().apply();
  168. Config::clash().data().save_config()?;
  169. Ok(())
  170. }
  171. Err(err) => {
  172. Config::clash().discard();
  173. Err(err)
  174. }
  175. }
  176. }
  177. /// 修改verge的订阅
  178. /// 一般都是一个个的修改
  179. pub async fn patch_verge(patch: IVerge) -> Result<()> {
  180. Config::verge().draft().patch_config(patch.clone());
  181. let tun_mode = patch.enable_tun_mode;
  182. let auto_launch = patch.enable_auto_launch;
  183. let system_proxy = patch.enable_system_proxy;
  184. let pac = patch.proxy_auto_config;
  185. let pac_content = patch.pac_file_content;
  186. let proxy_bypass = patch.system_proxy_bypass;
  187. let language = patch.language;
  188. let port = patch.verge_mixed_port;
  189. #[cfg(target_os = "macos")]
  190. let tray_icon = patch.tray_icon;
  191. let common_tray_icon = patch.common_tray_icon;
  192. let sysproxy_tray_icon = patch.sysproxy_tray_icon;
  193. let tun_tray_icon = patch.tun_tray_icon;
  194. #[cfg(not(target_os = "windows"))]
  195. let redir_enabled = patch.verge_redir_enabled;
  196. #[cfg(target_os = "linux")]
  197. let tproxy_enabled = patch.verge_tproxy_enabled;
  198. let socks_enabled = patch.verge_socks_enabled;
  199. let http_enabled = patch.verge_http_enabled;
  200. let res = {
  201. let service_mode = patch.enable_service_mode;
  202. if service_mode.is_some() {
  203. log::debug!(target: "app", "change service mode to {}", service_mode.unwrap());
  204. Config::generate()?;
  205. CoreManager::global().run_core().await?;
  206. } else if tun_mode.is_some() {
  207. update_core_config().await?;
  208. }
  209. #[cfg(not(target_os = "windows"))]
  210. if redir_enabled.is_some() {
  211. Config::generate()?;
  212. CoreManager::global().run_core().await?;
  213. }
  214. #[cfg(target_os = "linux")]
  215. if tproxy_enabled.is_some() {
  216. Config::generate()?;
  217. CoreManager::global().run_core().await?;
  218. }
  219. if socks_enabled.is_some() || http_enabled.is_some() {
  220. Config::generate()?;
  221. CoreManager::global().run_core().await?;
  222. }
  223. if auto_launch.is_some() {
  224. sysopt::Sysopt::global().update_launch()?;
  225. }
  226. if system_proxy.is_some()
  227. || proxy_bypass.is_some()
  228. || port.is_some()
  229. || pac.is_some()
  230. || pac_content.is_some()
  231. {
  232. sysopt::Sysopt::global().update_sysproxy()?;
  233. sysopt::Sysopt::global().guard_proxy();
  234. }
  235. if let Some(true) = patch.enable_proxy_guard {
  236. sysopt::Sysopt::global().guard_proxy();
  237. }
  238. if let Some(hotkeys) = patch.hotkeys {
  239. hotkey::Hotkey::global().update(hotkeys)?;
  240. }
  241. if language.is_some() {
  242. handle::Handle::update_systray()?;
  243. } else if system_proxy.is_some()
  244. || tun_mode.is_some()
  245. || common_tray_icon.is_some()
  246. || sysproxy_tray_icon.is_some()
  247. || tun_tray_icon.is_some()
  248. {
  249. handle::Handle::update_systray_part()?;
  250. }
  251. #[cfg(target_os = "macos")]
  252. if tray_icon.is_some() {
  253. handle::Handle::update_systray_part()?;
  254. }
  255. <Result<()>>::Ok(())
  256. };
  257. match res {
  258. Ok(()) => {
  259. Config::verge().apply();
  260. Config::verge().data().save_file()?;
  261. Ok(())
  262. }
  263. Err(err) => {
  264. Config::verge().discard();
  265. Err(err)
  266. }
  267. }
  268. }
  269. /// 更新某个profile
  270. /// 如果更新当前订阅就激活订阅
  271. pub async fn update_profile(uid: String, option: Option<PrfOption>) -> Result<()> {
  272. let url_opt = {
  273. let profiles = Config::profiles();
  274. let profiles = profiles.latest();
  275. let item = profiles.get_item(&uid)?;
  276. let is_remote = item.itype.as_ref().map_or(false, |s| s == "remote");
  277. if !is_remote {
  278. None // 直接更新
  279. } else if item.url.is_none() {
  280. bail!("failed to get the profile item url");
  281. } else {
  282. Some((item.url.clone().unwrap(), item.option.clone()))
  283. }
  284. };
  285. let should_update = match url_opt {
  286. Some((url, opt)) => {
  287. let merged_opt = PrfOption::merge(opt, option);
  288. let item = PrfItem::from_url(&url, None, None, merged_opt).await?;
  289. let profiles = Config::profiles();
  290. let mut profiles = profiles.latest();
  291. profiles.update_item(uid.clone(), item)?;
  292. Some(uid) == profiles.get_current()
  293. }
  294. None => true,
  295. };
  296. if should_update {
  297. update_core_config().await?;
  298. }
  299. Ok(())
  300. }
  301. /// 更新订阅
  302. async fn update_core_config() -> Result<()> {
  303. match CoreManager::global().update_config().await {
  304. Ok(_) => {
  305. handle::Handle::refresh_clash();
  306. handle::Handle::notice_message("set_config::ok", "ok");
  307. Ok(())
  308. }
  309. Err(err) => {
  310. handle::Handle::notice_message("set_config::error", format!("{err}"));
  311. Err(err)
  312. }
  313. }
  314. }
  315. /// copy env variable
  316. pub fn copy_clash_env(app_handle: &AppHandle) {
  317. let port = { Config::verge().latest().verge_mixed_port.unwrap_or(7897) };
  318. let http_proxy = format!("http://127.0.0.1:{}", port);
  319. let socks5_proxy = format!("socks5://127.0.0.1:{}", port);
  320. let sh =
  321. format!("export https_proxy={http_proxy} http_proxy={http_proxy} all_proxy={socks5_proxy}");
  322. let cmd: String = format!("set http_proxy={http_proxy}\r\nset https_proxy={http_proxy}");
  323. let ps: String = format!("$env:HTTP_PROXY=\"{http_proxy}\"; $env:HTTPS_PROXY=\"{http_proxy}\"");
  324. let mut cliboard = app_handle.clipboard_manager();
  325. let env_type = { Config::verge().latest().env_type.clone() };
  326. let env_type = match env_type {
  327. Some(env_type) => env_type,
  328. None => {
  329. #[cfg(not(target_os = "windows"))]
  330. let default = "bash";
  331. #[cfg(target_os = "windows")]
  332. let default = "powershell";
  333. default.to_string()
  334. }
  335. };
  336. match env_type.as_str() {
  337. "bash" => cliboard.write_text(sh).unwrap_or_default(),
  338. "cmd" => cliboard.write_text(cmd).unwrap_or_default(),
  339. "powershell" => cliboard.write_text(ps).unwrap_or_default(),
  340. _ => log::error!(target: "app", "copy_clash_env: Invalid env type! {env_type}"),
  341. };
  342. }
  343. pub async fn test_delay(url: String) -> Result<u32> {
  344. use tokio::time::{Duration, Instant};
  345. let mut builder = reqwest::ClientBuilder::new().use_rustls_tls().no_proxy();
  346. let port = Config::verge()
  347. .latest()
  348. .verge_mixed_port
  349. .unwrap_or(Config::clash().data().get_mixed_port());
  350. let tun_mode = Config::verge().latest().enable_tun_mode.unwrap_or(false);
  351. let proxy_scheme = format!("http://127.0.0.1:{port}");
  352. if !tun_mode {
  353. if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) {
  354. builder = builder.proxy(proxy);
  355. }
  356. if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) {
  357. builder = builder.proxy(proxy);
  358. }
  359. if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) {
  360. builder = builder.proxy(proxy);
  361. }
  362. }
  363. let request = builder
  364. .timeout(Duration::from_millis(10000))
  365. .build()?
  366. .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");
  367. let start = Instant::now();
  368. let response = request.send().await;
  369. match response {
  370. Ok(response) => {
  371. log::trace!(target: "app", "test_delay response: {:#?}", response);
  372. if response.status().is_success() {
  373. Ok(start.elapsed().as_millis() as u32)
  374. } else {
  375. Ok(10000u32)
  376. }
  377. }
  378. Err(err) => {
  379. log::trace!(target: "app", "test_delay error: {:#?}", err);
  380. Err(err.into())
  381. }
  382. }
  383. }
  384. pub fn check_permission() -> Result<()> {
  385. #[cfg(target_os = "windows")]
  386. {
  387. let hklm = winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE);
  388. if let Ok(reg) = hklm.open_subkey_with_flags(
  389. "SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Run",
  390. winreg::enums::KEY_SET_VALUE,
  391. ) {
  392. reg.delete_value("Clash Verge").unwrap_or_default();
  393. return Ok(());
  394. }
  395. }
  396. Err(anyhow::anyhow!("permission denied"))
  397. }