cmds.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. use crate::{
  2. core::{ClashInfo, PrfItem, PrfOption, Profiles, VergeConfig},
  3. states::{ClashState, ProfilesState, VergeState},
  4. utils::{dirs, sysopt::SysProxyConfig},
  5. };
  6. use crate::{ret_err, wrap_err};
  7. use anyhow::Result;
  8. use serde_yaml::Mapping;
  9. use std::{path::PathBuf, process::Command};
  10. use tauri::{api, Manager, State};
  11. /// get all profiles from `profiles.yaml`
  12. #[tauri::command]
  13. pub fn get_profiles<'a>(profiles_state: State<'_, ProfilesState>) -> Result<Profiles, String> {
  14. let profiles = profiles_state.0.lock().unwrap();
  15. Ok(profiles.clone())
  16. }
  17. /// synchronize data irregularly
  18. #[tauri::command]
  19. pub fn sync_profiles(profiles_state: State<'_, ProfilesState>) -> Result<(), String> {
  20. let mut profiles = profiles_state.0.lock().unwrap();
  21. wrap_err!(profiles.sync_file())
  22. }
  23. /// import the profile from url
  24. /// and save to `profiles.yaml`
  25. #[tauri::command]
  26. pub async fn import_profile(
  27. url: String,
  28. option: Option<PrfOption>,
  29. profiles_state: State<'_, ProfilesState>,
  30. ) -> Result<(), String> {
  31. let item = wrap_err!(PrfItem::from_url(&url, None, None, option).await)?;
  32. let mut profiles = profiles_state.0.lock().unwrap();
  33. wrap_err!(profiles.append_item(item))
  34. }
  35. /// new a profile
  36. /// append a temp profile item file to the `profiles` dir
  37. /// view the temp profile file by using vscode or other editor
  38. #[tauri::command]
  39. pub async fn create_profile(
  40. item: PrfItem, // partial
  41. file_data: Option<String>,
  42. profiles_state: State<'_, ProfilesState>,
  43. ) -> Result<(), String> {
  44. let item = wrap_err!(PrfItem::from(item, file_data).await)?;
  45. let mut profiles = profiles_state.0.lock().unwrap();
  46. wrap_err!(profiles.append_item(item))
  47. }
  48. /// Update the profile
  49. #[tauri::command]
  50. pub async fn update_profile(
  51. index: String,
  52. option: Option<PrfOption>,
  53. clash_state: State<'_, ClashState>,
  54. profiles_state: State<'_, ProfilesState>,
  55. ) -> Result<(), String> {
  56. let (url, opt) = {
  57. // must release the lock here
  58. let profiles = profiles_state.0.lock().unwrap();
  59. let item = wrap_err!(profiles.get_item(&index))?;
  60. // check the profile type
  61. if let Some(typ) = item.itype.as_ref() {
  62. if *typ != "remote" {
  63. ret_err!(format!("could not update the `{typ}` profile"));
  64. }
  65. }
  66. if item.url.is_none() {
  67. ret_err!("failed to get the item url");
  68. }
  69. (item.url.clone().unwrap(), item.option.clone())
  70. };
  71. let fetch_opt = PrfOption::merge(opt, option);
  72. let item = wrap_err!(PrfItem::from_url(&url, None, None, fetch_opt).await)?;
  73. let mut profiles = profiles_state.0.lock().unwrap();
  74. wrap_err!(profiles.update_item(index.clone(), item))?;
  75. // reactivate the profile
  76. if Some(index) == profiles.get_current() {
  77. let clash = clash_state.0.lock().unwrap();
  78. wrap_err!(clash.activate(&profiles, false))?;
  79. }
  80. Ok(())
  81. }
  82. /// change the current profile
  83. #[tauri::command]
  84. pub fn select_profile(
  85. index: String,
  86. clash_state: State<'_, ClashState>,
  87. profiles_state: State<'_, ProfilesState>,
  88. ) -> Result<(), String> {
  89. let mut profiles = profiles_state.0.lock().unwrap();
  90. wrap_err!(profiles.put_current(index))?;
  91. let clash = clash_state.0.lock().unwrap();
  92. wrap_err!(clash.activate(&profiles, false))
  93. }
  94. /// change the profile chain
  95. #[tauri::command]
  96. pub fn change_profile_chain(
  97. chain: Option<Vec<String>>,
  98. app_handle: tauri::AppHandle,
  99. clash_state: State<'_, ClashState>,
  100. profiles_state: State<'_, ProfilesState>,
  101. ) -> Result<(), String> {
  102. let mut clash = clash_state.0.lock().unwrap();
  103. let mut profiles = profiles_state.0.lock().unwrap();
  104. profiles.put_chain(chain);
  105. clash.set_window(app_handle.get_window("main"));
  106. wrap_err!(clash.activate_enhanced(&profiles, false))
  107. }
  108. /// manually exec enhanced profile
  109. #[tauri::command]
  110. pub fn enhance_profiles(
  111. app_handle: tauri::AppHandle,
  112. clash_state: State<'_, ClashState>,
  113. profiles_state: State<'_, ProfilesState>,
  114. ) -> Result<(), String> {
  115. let mut clash = clash_state.0.lock().unwrap();
  116. let profiles = profiles_state.0.lock().unwrap();
  117. clash.set_window(app_handle.get_window("main"));
  118. wrap_err!(clash.activate_enhanced(&profiles, false))
  119. }
  120. /// delete profile item
  121. #[tauri::command]
  122. pub fn delete_profile(
  123. index: String,
  124. clash_state: State<'_, ClashState>,
  125. profiles_state: State<'_, ProfilesState>,
  126. ) -> Result<(), String> {
  127. let mut profiles = profiles_state.0.lock().unwrap();
  128. if wrap_err!(profiles.delete_item(index))? {
  129. let clash = clash_state.0.lock().unwrap();
  130. wrap_err!(clash.activate(&profiles, false))?;
  131. }
  132. Ok(())
  133. }
  134. /// patch the profile config
  135. #[tauri::command]
  136. pub fn patch_profile(
  137. index: String,
  138. profile: PrfItem,
  139. profiles_state: State<'_, ProfilesState>,
  140. ) -> Result<(), String> {
  141. let mut profiles = profiles_state.0.lock().unwrap();
  142. wrap_err!(profiles.patch_item(index, profile))
  143. }
  144. /// run vscode command to edit the profile
  145. #[tauri::command]
  146. pub fn view_profile(index: String, profiles_state: State<'_, ProfilesState>) -> Result<(), String> {
  147. let profiles = profiles_state.0.lock().unwrap();
  148. let item = wrap_err!(profiles.get_item(&index))?;
  149. let file = item.file.clone();
  150. if file.is_none() {
  151. ret_err!("the file is null");
  152. }
  153. let path = dirs::app_profiles_dir().join(file.unwrap());
  154. if !path.exists() {
  155. ret_err!("the file not found");
  156. }
  157. // use vscode first
  158. if let Ok(code) = which::which("code") {
  159. #[cfg(target_os = "windows")]
  160. {
  161. use std::os::windows::process::CommandExt;
  162. if let Err(err) = Command::new(code)
  163. .creation_flags(0x08000000)
  164. .arg(path)
  165. .spawn()
  166. {
  167. log::error!("{err}");
  168. return Err("failed to open file by VScode".into());
  169. }
  170. }
  171. #[cfg(not(target_os = "windows"))]
  172. if let Err(err) = Command::new(code).arg(path).spawn() {
  173. log::error!("{err}");
  174. return Err("failed to open file by VScode".into());
  175. }
  176. return Ok(());
  177. }
  178. open_path_cmd(path, "failed to open file by `open`")
  179. }
  180. /// restart the sidecar
  181. #[tauri::command]
  182. pub fn restart_sidecar(
  183. clash_state: State<'_, ClashState>,
  184. profiles_state: State<'_, ProfilesState>,
  185. ) -> Result<(), String> {
  186. let mut clash = clash_state.0.lock().unwrap();
  187. let mut profiles = profiles_state.0.lock().unwrap();
  188. wrap_err!(clash.restart_sidecar(&mut profiles))
  189. }
  190. /// get the clash core info from the state
  191. /// the caller can also get the infomation by clash's api
  192. #[tauri::command]
  193. pub fn get_clash_info(clash_state: State<'_, ClashState>) -> Result<ClashInfo, String> {
  194. let clash = clash_state.0.lock().unwrap();
  195. Ok(clash.info.clone())
  196. }
  197. /// update the clash core config
  198. /// after putting the change to the clash core
  199. /// then we should save the latest config
  200. #[tauri::command]
  201. pub fn patch_clash_config(
  202. payload: Mapping,
  203. clash_state: State<'_, ClashState>,
  204. verge_state: State<'_, VergeState>,
  205. profiles_state: State<'_, ProfilesState>,
  206. ) -> Result<(), String> {
  207. let mut clash = clash_state.0.lock().unwrap();
  208. let mut verge = verge_state.0.lock().unwrap();
  209. let mut profiles = profiles_state.0.lock().unwrap();
  210. wrap_err!(clash.patch_config(payload, &mut verge, &mut profiles))
  211. }
  212. /// get the system proxy
  213. #[tauri::command]
  214. pub fn get_sys_proxy() -> Result<SysProxyConfig, String> {
  215. wrap_err!(SysProxyConfig::get_sys())
  216. }
  217. /// get the current proxy config
  218. /// which may not the same as system proxy
  219. #[tauri::command]
  220. pub fn get_cur_proxy(verge_state: State<'_, VergeState>) -> Result<Option<SysProxyConfig>, String> {
  221. let verge = verge_state.0.lock().unwrap();
  222. Ok(verge.cur_sysproxy.clone())
  223. }
  224. /// get the verge config
  225. #[tauri::command]
  226. pub fn get_verge_config(verge_state: State<'_, VergeState>) -> Result<VergeConfig, String> {
  227. let verge = verge_state.0.lock().unwrap();
  228. let mut config = verge.config.clone();
  229. if config.system_proxy_bypass.is_none() && verge.cur_sysproxy.is_some() {
  230. config.system_proxy_bypass = Some(verge.cur_sysproxy.clone().unwrap().bypass)
  231. }
  232. Ok(config)
  233. }
  234. /// patch the verge config
  235. /// this command only save the config and not responsible for other things
  236. #[tauri::command]
  237. pub fn patch_verge_config(
  238. payload: VergeConfig,
  239. app_handle: tauri::AppHandle,
  240. clash_state: State<'_, ClashState>,
  241. verge_state: State<'_, VergeState>,
  242. profiles_state: State<'_, ProfilesState>,
  243. ) -> Result<(), String> {
  244. let tun_mode = payload.enable_tun_mode.clone();
  245. let system_proxy = payload.enable_system_proxy.clone();
  246. // change tun mode
  247. if tun_mode.is_some() {
  248. let mut clash = clash_state.0.lock().unwrap();
  249. let profiles = profiles_state.0.lock().unwrap();
  250. wrap_err!(clash.tun_mode(tun_mode.unwrap()))?;
  251. clash.update_config();
  252. wrap_err!(clash.activate(&profiles, false))?;
  253. }
  254. let mut verge = verge_state.0.lock().unwrap();
  255. wrap_err!(verge.patch_config(payload))?;
  256. // change system tray
  257. if system_proxy.is_some() {
  258. app_handle
  259. .tray_handle()
  260. .get_item("system_proxy")
  261. .set_selected(system_proxy.unwrap())
  262. .unwrap();
  263. }
  264. Ok(())
  265. }
  266. /// kill all sidecars when update app
  267. #[tauri::command]
  268. pub fn kill_sidecars() {
  269. api::process::kill_children();
  270. }
  271. /// open app config dir
  272. #[tauri::command]
  273. pub fn open_app_dir() -> Result<(), String> {
  274. let app_dir = dirs::app_home_dir();
  275. open_path_cmd(app_dir, "failed to open app dir")
  276. }
  277. /// open logs dir
  278. #[tauri::command]
  279. pub fn open_logs_dir() -> Result<(), String> {
  280. let log_dir = dirs::app_logs_dir();
  281. open_path_cmd(log_dir, "failed to open logs dir")
  282. }
  283. /// use the os default open command to open file or dir
  284. fn open_path_cmd(path: PathBuf, err_str: &str) -> Result<(), String> {
  285. let result;
  286. #[cfg(target_os = "windows")]
  287. {
  288. use std::os::windows::process::CommandExt;
  289. result = Command::new("explorer")
  290. .creation_flags(0x08000000)
  291. .arg(&path)
  292. .spawn();
  293. }
  294. #[cfg(target_os = "macos")]
  295. {
  296. result = Command::new("open").arg(&path).spawn();
  297. }
  298. #[cfg(target_os = "linux")]
  299. {
  300. result = Command::new("xdg-open").arg(&path).spawn();
  301. }
  302. match result {
  303. Ok(child) => match child.wait_with_output() {
  304. Ok(out) => {
  305. // 退出码不为0 不一定没有调用成功
  306. // 因此仅做warn log且不返回错误
  307. if let Some(code) = out.status.code() {
  308. if code != 0 {
  309. log::warn!("failed to open {:?} (code {})", &path, code);
  310. log::warn!(
  311. "open cmd stdout: {}, stderr: {}",
  312. String::from_utf8_lossy(&out.stdout),
  313. String::from_utf8_lossy(&out.stderr),
  314. );
  315. }
  316. }
  317. }
  318. Err(err) => {
  319. log::error!("failed to open {:?} for {err}", &path);
  320. return Err(err_str.into());
  321. }
  322. },
  323. Err(err) => {
  324. log::error!("failed to open {:?} for {err}", &path);
  325. return Err(err_str.into());
  326. }
  327. }
  328. return Ok(());
  329. }