cmds.rs 9.1 KB

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