cmds.rs 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. use crate::{
  2. core::{ClashInfo, PrfItem, Profiles, VergeConfig},
  3. ret_err,
  4. states::{ClashState, ProfilesState, VergeState},
  5. utils::{dirs, sysopt::SysProxyConfig},
  6. wrap_err,
  7. };
  8. use anyhow::Result;
  9. use serde_yaml::Mapping;
  10. use std::{path::PathBuf, process::Command};
  11. use tauri::{api, State};
  12. /// get all profiles from `profiles.yaml`
  13. #[tauri::command]
  14. pub fn get_profiles<'a>(profiles_state: State<'_, ProfilesState>) -> Result<Profiles, String> {
  15. let profiles = profiles_state.0.lock().unwrap();
  16. Ok(profiles.clone())
  17. }
  18. /// synchronize data irregularly
  19. #[tauri::command]
  20. pub fn sync_profiles(profiles_state: State<'_, ProfilesState>) -> Result<(), String> {
  21. let mut profiles = profiles_state.0.lock().unwrap();
  22. wrap_err!(profiles.sync_file())
  23. }
  24. /// import the profile from url
  25. /// and save to `profiles.yaml`
  26. #[tauri::command]
  27. pub async fn import_profile(
  28. url: String,
  29. with_proxy: bool,
  30. profiles_state: State<'_, ProfilesState>,
  31. ) -> Result<(), String> {
  32. let item = wrap_err!(PrfItem::from_url(&url, with_proxy).await)?;
  33. let mut profiles = profiles_state.0.lock().unwrap();
  34. wrap_err!(profiles.append_item(item))
  35. }
  36. /// new a profile
  37. /// append a temp profile item file to the `profiles` dir
  38. /// view the temp profile file by using vscode or other editor
  39. #[tauri::command]
  40. pub async fn new_profile(
  41. name: String,
  42. desc: String,
  43. profiles_state: State<'_, ProfilesState>,
  44. ) -> Result<(), String> {
  45. let item = wrap_err!(PrfItem::from_local(name, desc))?;
  46. let mut profiles = profiles_state.0.lock().unwrap();
  47. wrap_err!(profiles.append_item(item))
  48. }
  49. /// Update the profile
  50. #[tauri::command]
  51. pub async fn update_profile(
  52. index: String,
  53. with_proxy: bool,
  54. clash_state: State<'_, ClashState>,
  55. profiles_state: State<'_, ProfilesState>,
  56. ) -> Result<(), String> {
  57. let url = {
  58. // must release the lock here
  59. let profiles = profiles_state.0.lock().unwrap();
  60. let item = wrap_err!(profiles.get_item(&index))?;
  61. if item.url.is_none() {
  62. ret_err!("failed to get the item url");
  63. }
  64. item.url.clone().unwrap()
  65. };
  66. let item = wrap_err!(PrfItem::from_url(&url, with_proxy).await)?;
  67. let mut profiles = profiles_state.0.lock().unwrap();
  68. wrap_err!(profiles.update_item(index.clone(), item))?;
  69. // reactivate the profile
  70. if Some(index) == profiles.get_current() {
  71. let clash = clash_state.0.lock().unwrap();
  72. wrap_err!(clash.activate(&profiles))?;
  73. }
  74. Ok(())
  75. }
  76. /// change the current profile
  77. #[tauri::command]
  78. pub fn select_profile(
  79. index: String,
  80. clash_state: State<'_, ClashState>,
  81. profiles_state: State<'_, ProfilesState>,
  82. ) -> Result<(), String> {
  83. let mut profiles = profiles_state.0.lock().unwrap();
  84. wrap_err!(profiles.put_current(index))?;
  85. let clash = clash_state.0.lock().unwrap();
  86. wrap_err!(clash.activate(&profiles))
  87. }
  88. /// delete profile item
  89. #[tauri::command]
  90. pub fn delete_profile(
  91. index: String,
  92. clash_state: State<'_, ClashState>,
  93. profiles_state: State<'_, ProfilesState>,
  94. ) -> Result<(), String> {
  95. let mut profiles = profiles_state.0.lock().unwrap();
  96. if wrap_err!(profiles.delete_item(index))? {
  97. let clash = clash_state.0.lock().unwrap();
  98. wrap_err!(clash.activate(&profiles))?;
  99. }
  100. Ok(())
  101. }
  102. /// patch the profile config
  103. #[tauri::command]
  104. pub fn patch_profile(
  105. index: String,
  106. profile: PrfItem,
  107. profiles_state: State<'_, ProfilesState>,
  108. ) -> Result<(), String> {
  109. let mut profiles = profiles_state.0.lock().unwrap();
  110. wrap_err!(profiles.patch_item(index, profile))
  111. }
  112. /// run vscode command to edit the profile
  113. #[tauri::command]
  114. pub fn view_profile(index: String, profiles_state: State<'_, ProfilesState>) -> Result<(), String> {
  115. let profiles = profiles_state.0.lock().unwrap();
  116. let item = wrap_err!(profiles.get_item(&index))?;
  117. let file = item.file.clone();
  118. if file.is_none() {
  119. ret_err!("the file is null");
  120. }
  121. let path = dirs::app_profiles_dir().join(file.unwrap());
  122. if !path.exists() {
  123. ret_err!("the file not found");
  124. }
  125. // use vscode first
  126. if let Ok(code) = which::which("code") {
  127. #[cfg(target_os = "windows")]
  128. {
  129. use std::os::windows::process::CommandExt;
  130. return match Command::new(code)
  131. .creation_flags(0x08000000)
  132. .arg(path)
  133. .spawn()
  134. {
  135. Ok(_) => Ok(()),
  136. Err(_) => Err("failed to open file by VScode".into()),
  137. };
  138. }
  139. #[cfg(not(target_os = "windows"))]
  140. return match Command::new(code).arg(path).spawn() {
  141. Ok(_) => Ok(()),
  142. Err(_) => Err("failed to open file by VScode".into()),
  143. };
  144. }
  145. open_path_cmd(path, "failed to open file by `open`")
  146. }
  147. /// restart the sidecar
  148. #[tauri::command]
  149. pub fn restart_sidecar(
  150. clash_state: State<'_, ClashState>,
  151. profiles_state: State<'_, ProfilesState>,
  152. ) -> Result<(), String> {
  153. let mut clash = clash_state.0.lock().unwrap();
  154. let mut profiles = profiles_state.0.lock().unwrap();
  155. wrap_err!(clash.restart_sidecar(&mut profiles))
  156. }
  157. /// get the clash core info from the state
  158. /// the caller can also get the infomation by clash's api
  159. #[tauri::command]
  160. pub fn get_clash_info(clash_state: State<'_, ClashState>) -> Result<ClashInfo, String> {
  161. let clash = clash_state.0.lock().unwrap();
  162. Ok(clash.info.clone())
  163. }
  164. /// update the clash core config
  165. /// after putting the change to the clash core
  166. /// then we should save the latest config
  167. #[tauri::command]
  168. pub fn patch_clash_config(
  169. payload: Mapping,
  170. clash_state: State<'_, ClashState>,
  171. verge_state: State<'_, VergeState>,
  172. profiles_state: State<'_, ProfilesState>,
  173. ) -> Result<(), String> {
  174. let mut clash = clash_state.0.lock().unwrap();
  175. let mut verge = verge_state.0.lock().unwrap();
  176. let mut profiles = profiles_state.0.lock().unwrap();
  177. wrap_err!(clash.patch_config(payload, &mut verge, &mut profiles))
  178. }
  179. /// get the system proxy
  180. #[tauri::command]
  181. pub fn get_sys_proxy() -> Result<SysProxyConfig, String> {
  182. wrap_err!(SysProxyConfig::get_sys())
  183. }
  184. /// get the current proxy config
  185. /// which may not the same as system proxy
  186. #[tauri::command]
  187. pub fn get_cur_proxy(verge_state: State<'_, VergeState>) -> Result<Option<SysProxyConfig>, String> {
  188. let verge = verge_state.0.lock().unwrap();
  189. Ok(verge.cur_sysproxy.clone())
  190. }
  191. /// get the verge config
  192. #[tauri::command]
  193. pub fn get_verge_config(verge_state: State<'_, VergeState>) -> Result<VergeConfig, String> {
  194. let verge = verge_state.0.lock().unwrap();
  195. let mut config = verge.config.clone();
  196. if config.system_proxy_bypass.is_none() && verge.cur_sysproxy.is_some() {
  197. config.system_proxy_bypass = Some(verge.cur_sysproxy.clone().unwrap().bypass)
  198. }
  199. Ok(config)
  200. }
  201. /// patch the verge config
  202. /// this command only save the config and not responsible for other things
  203. #[tauri::command]
  204. pub fn patch_verge_config(
  205. payload: VergeConfig,
  206. clash_state: State<'_, ClashState>,
  207. verge_state: State<'_, VergeState>,
  208. profiles_state: State<'_, ProfilesState>,
  209. ) -> Result<(), String> {
  210. let tun_mode = payload.enable_tun_mode.clone();
  211. let mut verge = verge_state.0.lock().unwrap();
  212. wrap_err!(verge.patch_config(payload))?;
  213. // change tun mode
  214. if tun_mode.is_some() {
  215. let mut clash = clash_state.0.lock().unwrap();
  216. let profiles = profiles_state.0.lock().unwrap();
  217. wrap_err!(clash.tun_mode(tun_mode.unwrap()))?;
  218. clash.update_config();
  219. wrap_err!(clash.activate(&profiles))?;
  220. }
  221. Ok(())
  222. }
  223. /// kill all sidecars when update app
  224. #[tauri::command]
  225. pub fn kill_sidecars() {
  226. api::process::kill_children();
  227. }
  228. /// open app config dir
  229. #[tauri::command]
  230. pub fn open_app_dir() -> Result<(), String> {
  231. let app_dir = dirs::app_home_dir();
  232. open_path_cmd(app_dir, "failed to open app dir")
  233. }
  234. /// open logs dir
  235. #[tauri::command]
  236. pub fn open_logs_dir() -> Result<(), String> {
  237. let log_dir = dirs::app_logs_dir();
  238. open_path_cmd(log_dir, "failed to open logs dir")
  239. }
  240. /// get open/explorer command
  241. fn open_path_cmd(dir: PathBuf, err_str: &str) -> Result<(), String> {
  242. #[cfg(target_os = "windows")]
  243. {
  244. use std::os::windows::process::CommandExt;
  245. match Command::new("explorer")
  246. .creation_flags(0x08000000)
  247. .arg(dir)
  248. .spawn()
  249. {
  250. Ok(_) => Ok(()),
  251. Err(_) => Err(err_str.into()),
  252. }
  253. }
  254. #[cfg(not(target_os = "windows"))]
  255. match Command::new("open").arg(dir).spawn() {
  256. Ok(_) => Ok(()),
  257. Err(_) => Err(err_str.into()),
  258. }
  259. }