cmds.rs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  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::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_enhanced(&profiles, false, 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_enhanced(&profiles, false, 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, 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, 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_enhanced(&profiles, false, 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!("failed to open file by VScode for {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!("failed to open file by VScode for {err}");
  174. return Err("failed to open file by VScode".into());
  175. }
  176. return Ok(());
  177. }
  178. wrap_err!(open::that(path))
  179. }
  180. /// read the profile item file data
  181. #[tauri::command]
  182. pub fn read_profile_file(
  183. index: String,
  184. profiles_state: State<'_, ProfilesState>,
  185. ) -> Result<String, String> {
  186. let profiles = profiles_state.0.lock().unwrap();
  187. let item = wrap_err!(profiles.get_item(&index))?;
  188. let data = wrap_err!(item.read_file())?;
  189. Ok(data)
  190. }
  191. /// save the profile item file data
  192. #[tauri::command]
  193. pub fn save_profile_file(
  194. index: String,
  195. file_data: Option<String>,
  196. profiles_state: State<'_, ProfilesState>,
  197. ) -> Result<(), String> {
  198. if file_data.is_none() {
  199. return Ok(());
  200. }
  201. let profiles = profiles_state.0.lock().unwrap();
  202. let item = wrap_err!(profiles.get_item(&index))?;
  203. wrap_err!(item.save_file(file_data.unwrap()))
  204. }
  205. /// restart the sidecar
  206. #[tauri::command]
  207. pub fn restart_sidecar(
  208. clash_state: State<'_, ClashState>,
  209. profiles_state: State<'_, ProfilesState>,
  210. ) -> Result<(), String> {
  211. let mut clash = clash_state.0.lock().unwrap();
  212. let mut profiles = profiles_state.0.lock().unwrap();
  213. wrap_err!(clash.restart_sidecar(&mut profiles))
  214. }
  215. /// get the clash core info from the state
  216. /// the caller can also get the infomation by clash's api
  217. #[tauri::command]
  218. pub fn get_clash_info(clash_state: State<'_, ClashState>) -> Result<ClashInfo, String> {
  219. let clash = clash_state.0.lock().unwrap();
  220. Ok(clash.info.clone())
  221. }
  222. /// update the clash core config
  223. /// after putting the change to the clash core
  224. /// then we should save the latest config
  225. #[tauri::command]
  226. pub fn patch_clash_config(
  227. payload: Mapping,
  228. clash_state: State<'_, ClashState>,
  229. verge_state: State<'_, VergeState>,
  230. profiles_state: State<'_, ProfilesState>,
  231. ) -> Result<(), String> {
  232. let mut clash = clash_state.0.lock().unwrap();
  233. let mut verge = verge_state.0.lock().unwrap();
  234. let mut profiles = profiles_state.0.lock().unwrap();
  235. wrap_err!(clash.patch_config(payload, &mut verge, &mut profiles))
  236. }
  237. /// get the system proxy
  238. #[tauri::command]
  239. pub fn get_sys_proxy() -> Result<SysProxyConfig, String> {
  240. wrap_err!(SysProxyConfig::get_sys())
  241. }
  242. /// get the current proxy config
  243. /// which may not the same as system proxy
  244. #[tauri::command]
  245. pub fn get_cur_proxy(verge_state: State<'_, VergeState>) -> Result<Option<SysProxyConfig>, String> {
  246. let verge = verge_state.0.lock().unwrap();
  247. Ok(verge.cur_sysproxy.clone())
  248. }
  249. /// get the verge config
  250. #[tauri::command]
  251. pub fn get_verge_config(verge_state: State<'_, VergeState>) -> Result<VergeConfig, String> {
  252. let verge = verge_state.0.lock().unwrap();
  253. let mut config = verge.config.clone();
  254. if config.system_proxy_bypass.is_none() && verge.cur_sysproxy.is_some() {
  255. config.system_proxy_bypass = Some(verge.cur_sysproxy.clone().unwrap().bypass)
  256. }
  257. Ok(config)
  258. }
  259. /// patch the verge config
  260. /// this command only save the config and not responsible for other things
  261. #[tauri::command]
  262. pub fn patch_verge_config(
  263. payload: VergeConfig,
  264. app_handle: tauri::AppHandle,
  265. clash_state: State<'_, ClashState>,
  266. verge_state: State<'_, VergeState>,
  267. profiles_state: State<'_, ProfilesState>,
  268. ) -> Result<(), String> {
  269. let tun_mode = payload.enable_tun_mode.clone();
  270. let system_proxy = payload.enable_system_proxy.clone();
  271. let mut verge = verge_state.0.lock().unwrap();
  272. wrap_err!(verge.patch_config(payload))?;
  273. // change system tray
  274. if system_proxy.is_some() {
  275. app_handle
  276. .tray_handle()
  277. .get_item("system_proxy")
  278. .set_selected(system_proxy.unwrap())
  279. .unwrap();
  280. }
  281. // change tun mode
  282. if tun_mode.is_some() {
  283. #[cfg(target_os = "windows")]
  284. if *tun_mode.as_ref().unwrap() {
  285. let wintun_dll = dirs::app_home_dir().join("wintun.dll");
  286. if !wintun_dll.exists() {
  287. log::error!("failed to enable TUN for missing `wintun.dll`");
  288. return Err("failed to enable TUN for missing `wintun.dll`".into());
  289. }
  290. }
  291. let clash = clash_state.0.lock().unwrap();
  292. let profiles = profiles_state.0.lock().unwrap();
  293. wrap_err!(clash.activate_enhanced(&profiles, false, false))?;
  294. }
  295. Ok(())
  296. }
  297. /// kill all sidecars when update app
  298. #[tauri::command]
  299. pub fn kill_sidecars() {
  300. api::process::kill_children();
  301. }
  302. /// open app config dir
  303. #[tauri::command]
  304. pub fn open_app_dir() -> Result<(), String> {
  305. let app_dir = dirs::app_home_dir();
  306. wrap_err!(open::that(app_dir))
  307. }
  308. /// open logs dir
  309. #[tauri::command]
  310. pub fn open_logs_dir() -> Result<(), String> {
  311. let log_dir = dirs::app_logs_dir();
  312. wrap_err!(open::that(log_dir))
  313. }