cmds.rs 10 KB

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