diff --git a/clashtui/Cargo.lock b/clashtui/Cargo.lock index ec28539..2e6dd32 100644 --- a/clashtui/Cargo.lock +++ b/clashtui/Cargo.lock @@ -86,7 +86,7 @@ dependencies = [ "argh_shared", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.53", ] [[package]] @@ -186,7 +186,6 @@ dependencies = [ "argh", "encoding", "enumflags2", - "libc", "log", "log4rs", "nix", @@ -341,7 +340,7 @@ checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.53", ] [[package]] @@ -758,7 +757,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.53", ] [[package]] @@ -774,9 +773,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.32" +version = "0.9.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f" +checksum = "a0623d197252096520c6f2a5e1171ee436e5af99a5d7caa2891e55e61950e6d9" dependencies = [ "indexmap", "itoa", @@ -862,7 +861,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.52", + "syn 2.0.53", ] [[package]] @@ -878,9 +877,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.52" +version = "2.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" dependencies = [ "proc-macro2", "quote", @@ -904,7 +903,7 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.53", ] [[package]] @@ -931,7 +930,7 @@ name = "ui-derive" version = "0.1.1" dependencies = [ "quote", - "syn 2.0.52", + "syn 2.0.53", ] [[package]] @@ -954,9 +953,9 @@ checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "unsafe-libyaml" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "untrusted" @@ -997,7 +996,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.53", "wasm-bindgen-shared", ] @@ -1019,7 +1018,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.53", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1216,5 +1215,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.53", ] diff --git a/clashtui/Cargo.toml b/clashtui/Cargo.toml index 41bd2cd..6cd5081 100644 --- a/clashtui/Cargo.toml +++ b/clashtui/Cargo.toml @@ -30,10 +30,7 @@ log = "0.4" log4rs = {version = "1.3", default-features = false, features = ["pattern_encoder", "file_appender"]} enumflags2 = "0.7.9" nix = {version = "0.28.0", features = ["fs", "user"]} -libc = "0.2.153" regex = "1.10.3" - -[target.'cfg(target_os = "windows")'.dependencies] encoding = "0.2.33" [workspace] @@ -55,6 +52,6 @@ section = "utility" priority = "optional" assets = [ ['target/release/clashtui', 'usr/bin/clashtui', '755'], - ['../README.md', 'usr/share/doc/hust-network-login/README.md', '644'], + ['../README.md', 'usr/share/doc/clashtui/README.md', '644'], ] maintainer-scripts = 'debian/' diff --git a/clashtui/api/src/clash.rs b/clashtui/api/src/clash.rs index 0d93c82..bbb2573 100644 --- a/clashtui/api/src/clash.rs +++ b/clashtui/api/src/clash.rs @@ -1,5 +1,7 @@ const DEFAULT_PAYLOAD: &str = "'{\"path\": \"\", \"payload\": \"\"}'"; -const TIMEOUT: u8 = 3; +//const TIMEOUT: u8 = 3; +const TIMEOUT: u8 = 10; // Adapting to poor network conditions. + // ToDo: Users can adjust settings based on their network quality. #[cfg(target_feature = "deprecated")] const GEO_URI: &str = "https://api.github.com/repos/MetaCubeX/meta-rules-dat/releases/latest"; #[cfg(target_feature = "deprecated")] diff --git a/clashtui/src/app.rs b/clashtui/src/app.rs index c002332..bd1e4cd 100644 --- a/clashtui/src/app.rs +++ b/clashtui/src/app.rs @@ -111,6 +111,7 @@ impl App { pub fn event(&mut self, ev: &event::Event) -> Result { let mut event_state = self.msgpopup.event(ev)?; + // Popup Priority Event Handling. Because popups always appear above non-popups. if event_state.is_notconsumed() { event_state = self.popup_event(ev)?; } @@ -250,39 +251,6 @@ impl App { } fn do_some_job_after_initapp_before_setupui(&mut self) { - // ## Correct the perm of files in clash_cfg_dir. - if ! self.clashtui_util.check_perms_of_ccd_files() { - let ccd_str = self.clashtui_util.tui_cfg.clash_cfg_dir.as_str(); - if ! utils::is_run_as_root() { - print!("The permissions of the '{}' files are incorrect. clashtui need to run as root to correct. Proceed with running as root? [Y/n] ", ccd_str); - std::io::stdout().flush().expect("Failed to flush stdout"); - - let mut input = String::new(); - let stdin = std::io::stdin(); - stdin.lock().read_line(&mut input).unwrap(); - - if input.trim().to_lowercase().as_str() == "y" { - utils::run_as_root(); - } - - } else { - if utils::is_clashtui_ep() { - println!("\nStart correct the permissions of files in '{}':\n", ccd_str); - let dir = std::path::Path::new(ccd_str); - if let Some(group_name) = utils::get_file_group_name(&dir.to_path_buf()) { - utils::restore_fileop_as_root(); - utils::modify_file_perms_in_dir(&dir.to_path_buf(), group_name.as_str()); - utils::mock_fileop_as_sudo_user(); - } - print!("\nEnd correct the permissions of files in '{}'. \n\nPress any key to continue. ", ccd_str); - std::io::stdout().flush().expect("Failed to flush stdout"); - let _ = std::io::stdin().read(&mut [0u8]); - } else { // user manually executing `sudo clashtui` - // Do nothing, as root is unaffected by permissions. - } - } - } - let cli_env: CliEnv = argh::from_env(); // ## CliMode @@ -291,7 +259,6 @@ impl App { if cli_env.update_all_profiles { is_cli_mode = true; - log::info!("Cron Mode!"); self.clashtui_util.get_profile_names() .unwrap() .into_iter() diff --git a/clashtui/src/main.rs b/clashtui/src/main.rs index ef70d5f..ec10b44 100644 --- a/clashtui/src/main.rs +++ b/clashtui/src/main.rs @@ -3,12 +3,11 @@ mod app; mod tui; mod utils; -use core::time::Duration; -use nix::sys; - use crate::app::App; use crate::utils::{Flag, Flags}; +use core::time::Duration; + pub const VERSION: &str = concat!(env!("CLASHTUI_VERSION")); fn main() { @@ -23,21 +22,10 @@ fn main() { let mut flags = Flags::empty(); - // ## Is CliMode - if cli_env.update_all_profiles { - flags.insert(Flag::CliMode) - } - // ## Setup logging as early as possible. So We can log. let config_dir = load_app_dir(&mut flags); setup_logging(config_dir.join("clashtui.log").to_str().unwrap()); - // To allow the mihomo process to read and write files created by clashtui in clash_cfg_dir, set the umask to 0o002and Users manually add SGID to clash_cfg_dir. - if utils::is_clashtui_ep() { - utils::mock_fileop_as_sudo_user(); - } - sys::stat::umask(sys::stat::Mode::from_bits_truncate(0o002)); - let tick_rate = 250; // time in ms between two ticks. if let Err(e) = run(&mut flags, tick_rate, &config_dir, &mut warning_list_msg) { eprintln!("{e}"); @@ -66,6 +54,7 @@ pub fn run(flags: &mut Flags, tick_rate: u64, config_dir: &std::path::Path Ok(()) } +use ui::event::KeyCode; use utils::CfgError; fn run_app( app: &mut App, @@ -98,6 +87,8 @@ fn run_app( app.late_event(); if event::poll(tick_rate)? { + //println!("{:?}", &event::read()?); // debug + if let Err(e) = app.event(&event::read()?) { app.popup_txt_msg(e.to_string()) }; @@ -117,11 +108,6 @@ fn load_app_dir(flags: &mut Flags) -> std::path::PathBuf { flags.insert(Flag::PortableMode); data_dir } else { - #[cfg(target_os = "linux")] - let clashtui_config_dir_str = env::var("XDG_CONFIG_HOME").map(|p| format!("{}/clashtui", p)) - .or_else(|_| env::var("HOME").map(|home| format!("{}/.config/clashtui", home))) - .unwrap(); - #[cfg(target_os = "windows")] let clashtui_config_dir_str = env::var("APPDATA") .map(|appdata| format!("{}/clashtui", appdata)) .unwrap(); diff --git a/clashtui/src/tui/tabs/clashsrvctl.rs b/clashtui/src/tui/tabs/clashsrvctl.rs index 783236b..e27c81a 100644 --- a/clashtui/src/tui/tabs/clashsrvctl.rs +++ b/clashtui/src/tui/tabs/clashsrvctl.rs @@ -31,18 +31,12 @@ impl ClashSrvCtlTab { pub fn new(clashtui_util: SharedClashTuiUtil, clashtui_state: SharedClashTuiState) -> Self { let mut operations = List::new(CLASHSRVCTL.to_string()); operations.set_items(vec![ - #[cfg(target_os = "linux")] - ClashSrvOp::SetPermission.into(), ClashSrvOp::StartClashService.into(), ClashSrvOp::StopClashService.into(), ClashSrvOp::SwitchMode.into(), - #[cfg(target_os = "windows")] ClashSrvOp::SwitchSysProxy.into(), - #[cfg(target_os = "windows")] ClashSrvOp::EnableLoopback.into(), - #[cfg(target_os = "windows")] ClashSrvOp::InstallSrv.into(), - #[cfg(target_os = "windows")] ClashSrvOp::UnInstallSrv.into(), ]); let mut modes = List::new("Mode".to_string()); @@ -69,24 +63,33 @@ impl super::TabEvent for ClashSrvCtlTab { if !self.is_visible { return Ok(EventState::NotConsumed); } - let event_state; + + let mut event_state; + if self.mode_selector.is_visible() { event_state = self.mode_selector.event(ev)?; - if event_state == EventState::WorkDone { + if event_state.is_consumed() { return Ok(event_state); } + if let ui::event::Event::Key(key) = ev { + // One-click key will trigger two KeyEventKind, Press and Release. + //log::info!("{:?}", ev); + if key.kind != ui::event::KeyEventKind::Press { + return Ok(EventState::NotConsumed); + } + if &Keys::Select == key { if let Some(new) = self.mode_selector.selected() { self.clashtui_state.borrow_mut().set_mode(new.clone()); } self.mode_selector.hide(); - } - if &Keys::Esc == key { + } else if &Keys::Esc == key { self.mode_selector.hide(); } + + return Ok(EventState::WorkDone); } - return Ok(EventState::WorkDone); } event_state = self.msgpopup.event(ev)?; @@ -97,7 +100,6 @@ impl super::TabEvent for ClashSrvCtlTab { if !self.is_visible { return Ok(EventState::NotConsumed); } - let event_state; if let ui::event::Event::Key(key) = ev { if key.kind != ui::event::KeyEventKind::Press { @@ -127,7 +129,6 @@ impl super::TabEvent for ClashSrvCtlTab { self.hide_msgpopup(); match op { ClashSrvOp::SwitchMode => unreachable!(), - #[cfg(target_os = "windows")] ClashSrvOp::SwitchSysProxy => { let cur = self .clashtui_state diff --git a/clashtui/src/tui/tabs/mod.rs b/clashtui/src/tui/tabs/mod.rs index e8974d3..06e15e4 100644 --- a/clashtui/src/tui/tabs/mod.rs +++ b/clashtui/src/tui/tabs/mod.rs @@ -98,17 +98,6 @@ macro_rules! define_enum { }; } -#[cfg(target_os = "linux")] -define_enum!( - pub ClashSrvOp, - [ - StartClashService, - StopClashService, - SetPermission, - SwitchMode - ] -); -#[cfg(target_os = "windows")] define_enum!( pub ClashSrvOp, [ diff --git a/clashtui/src/utils/ipc.rs b/clashtui/src/utils/ipc.rs index c47f114..193f191 100644 --- a/clashtui/src/utils/ipc.rs +++ b/clashtui/src/utils/ipc.rs @@ -1,4 +1,3 @@ -#[cfg(target_os = "windows")] use encoding::{all::GBK, DecoderTrap, Encoding}; use std::process::{Command, Output, Stdio}; @@ -20,16 +19,7 @@ pub fn spawn(pgm: &str, args: Vec<&str>) -> Result<()> { .spawn()?; Ok(()) } -#[cfg(target_os = "linux")] -pub fn exec_with_sbin(pgm: &str, args: Vec<&str>) -> Result { - log::debug!("LIPC: {} {:?}", pgm, args); - let mut path = std::env::var("PATH").unwrap_or_default(); - path.push_str(":/usr/sbin"); - let output = Command::new(pgm).env("PATH", path).args(args).output()?; - string_process_output(output) -} -#[cfg(target_os = "windows")] fn execute_powershell_script(script: &str) -> Result { string_process_output( Command::new("powershell") @@ -38,7 +28,7 @@ fn execute_powershell_script(script: &str) -> Result { .output()?, ) } -#[cfg(target_os = "windows")] + pub fn start_process_as_admin(path: &str, arg_list: &str, does_wait: bool) -> Result { let wait_op = if does_wait { "-Wait" } else { "" }; let arg_op = if arg_list.is_empty() { @@ -57,7 +47,6 @@ pub fn start_process_as_admin(path: &str, arg_list: &str, does_wait: bool) -> Re string_process_output(output) } -#[cfg(target_os = "windows")] pub fn execute_powershell_script_as_admin(cmd: &str, does_wait: bool) -> Result { let wait_op = if does_wait { "-Wait" } else { "" }; let cmd_op: String = if cmd.is_empty() { @@ -75,7 +64,7 @@ pub fn execute_powershell_script_as_admin(cmd: &str, does_wait: bool) -> Result< string_process_output(output) } -#[cfg(target_os = "windows")] + pub fn enable_system_proxy(proxy_addr: &str) -> Result { let enable_script = format!( r#" @@ -91,7 +80,6 @@ pub fn enable_system_proxy(proxy_addr: &str) -> Result { execute_powershell_script(&enable_script) } -#[cfg(target_os = "windows")] pub fn disable_system_proxy() -> Result { let disable_script = r#" $regPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings" @@ -103,7 +91,6 @@ pub fn disable_system_proxy() -> Result { execute_powershell_script(disable_script) } -#[cfg(target_os = "windows")] pub fn is_system_proxy_enabled() -> Result { let reg_query_output = Command::new("reg") .args(&[ @@ -124,7 +111,6 @@ pub fn is_system_proxy_enabled() -> Result { Ok(is_enabled) } -#[cfg(target_os = "windows")] fn string_process_output(output: Output) -> Result { let stdout_vec: Vec = output.stdout; use std::io::{Error, ErrorKind}; @@ -162,25 +148,4 @@ fn string_process_output(output: Output) -> Result { ); Ok(result_str) -} -#[cfg(target_os = "linux")] -fn string_process_output(output: Output) -> Result { - let stdout_str = String::from_utf8(output.stdout).unwrap(); - let stderr_str = String::from_utf8(output.stderr).unwrap(); - - let result_str = format!( - r#" - Status: - {} - - Stdout: - {} - - Stderr: - {} - "#, - output.status, stdout_str, stderr_str - ); - - Ok(result_str) -} +} \ No newline at end of file diff --git a/clashtui/src/utils/state.rs b/clashtui/src/utils/state.rs index 942d70b..9b38132 100644 --- a/clashtui/src/utils/state.rs +++ b/clashtui/src/utils/state.rs @@ -5,7 +5,6 @@ pub struct _State { pub profile: String, pub mode: Option, pub tun: Option, - #[cfg(target_os = "windows")] pub sysproxy: Option, } pub struct State { @@ -14,43 +13,22 @@ pub struct State { } impl State { pub fn new(ct: SharedClashTuiUtil) -> Self { - #[cfg(target_os = "windows")] return Self { st: ct.update_state(None, None, None), ct, }; - #[cfg(target_os = "linux")] - Self { - st: ct.update_state(None, None), - ct, - } } pub fn get_profile(&self) -> &String { &self.st.profile } pub fn set_profile(&mut self, profile: String) { // With update state - #[cfg(target_os = "windows")] - { - self.st = self.ct.update_state(Some(profile), None, None) - } - #[cfg(target_os = "linux")] - { - self.st = self.ct.update_state(Some(profile), None) - } + self.st = self.ct.update_state(Some(profile), None, None) } pub fn set_mode(&mut self, mode: String) { - #[cfg(target_os = "windows")] - { - self.st = self.ct.update_state(None, Some(mode), None) - } - #[cfg(target_os = "linux")] - { - self.st = self.ct.update_state(None, Some(mode)) - } + self.st = self.ct.update_state(None, Some(mode), None) } pub fn render(&self) -> String { - #[cfg(target_os = "windows")] let status_str = format!( "Profile: {} Mode: {} SysProxy: {} Tun: {} Help: ?", self.st.profile, @@ -60,20 +38,7 @@ impl State { .map_or("Unknown".to_string(), |v| format!("{}", v)), self.st .sysproxy - .map_or("Unknown".to_string(), |v| format!("{}", v)), - self.st - .tun - .as_ref() - .map_or("Unknown".to_string(), |v| format!("{}", v)), - ); - #[cfg(target_os = "linux")] - let status_str = format!( - "Profile: {} Mode: {} Tun: {} Help: ?", - self.st.profile, - self.st - .mode - .as_ref() - .map_or("Unknown".to_string(), |v| format!("{}", v)), + .map_or("Unknown".to_string(), |v| format!("{}", if v {"On"} else {"Off"})), self.st .tun .as_ref() @@ -81,11 +46,11 @@ impl State { ); status_str } - #[cfg(target_os = "windows")] + pub fn get_sysproxy(&self) -> Option { self.st.sysproxy } - #[cfg(target_os = "windows")] + pub fn set_sysproxy(&mut self, sysproxy: bool) { self.st = self.ct.update_state(None, None, Some(sysproxy)); } diff --git a/clashtui/src/utils/tui.rs b/clashtui/src/utils/tui.rs index 2c01063..2e7dcaf 100644 --- a/clashtui/src/utils/tui.rs +++ b/clashtui/src/utils/tui.rs @@ -138,6 +138,7 @@ fn load_app_config( .and_then(|v| v.as_str()) .unwrap_or("clash.meta") .to_string(); + log::info!("clash_ua: {}", clash_ua); let configs = if skip_init_conf { let config_path = clashtui_dir.join("config.yaml"); diff --git a/clashtui/src/utils/tui/impl_app.rs b/clashtui/src/utils/tui/impl_app.rs index 0c9cd5a..36af646 100644 --- a/clashtui/src/utils/tui/impl_app.rs +++ b/clashtui/src/utils/tui/impl_app.rs @@ -3,7 +3,6 @@ use crate::utils::state::_State; use std::path::Path; // IPC Related impl ClashTuiUtil { - #[cfg(target_os = "windows")] pub fn update_state( &self, new_pf: Option, @@ -34,16 +33,6 @@ impl ClashTuiUtil { } } - #[cfg(target_os = "linux")] - pub fn update_state(&self, new_pf: Option, new_mode: Option) -> _State { - let (pf, mode, tun) = self._update_state(new_pf, new_mode); - _State { - profile: pf, - mode, - tun, - } - } - pub fn fetch_recent_logs(&self, num_lines: usize) -> Vec { std::fs::read_to_string(self.clashtui_dir.join("clashtui.log")) .unwrap_or_default() @@ -60,15 +49,9 @@ impl ClashTuiUtil { use crate::utils::ipc::spawn; if !cmd.is_empty() { let opendir_cmd_with_path = cmd.replace("%s", path.to_str().unwrap_or("")); - #[cfg(target_os = "windows")] return spawn("cmd", vec!["/C", opendir_cmd_with_path.as_str()]); - #[cfg(target_os = "linux")] - spawn("sh", vec!["-c", opendir_cmd_with_path.as_str()]) } else { - #[cfg(target_os = "windows")] return spawn("cmd", vec!["/C", "start", path.to_str().unwrap_or("")]); - #[cfg(target_os = "linux")] - spawn("xdg-open", vec![path.to_str().unwrap_or("")]) } } diff --git a/clashtui/src/utils/tui/impl_clashsrv.rs b/clashtui/src/utils/tui/impl_clashsrv.rs index edc4d9e..35555a1 100644 --- a/clashtui/src/utils/tui/impl_clashsrv.rs +++ b/clashtui/src/utils/tui/impl_clashsrv.rs @@ -4,45 +4,6 @@ use crate::utils::ipc::{self, exec}; use std::io::Error; impl ClashTuiUtil { - #[cfg(target_os = "linux")] - pub fn clash_srv_ctl(&self, op: ClashSrvOp) -> Result { - match op { - ClashSrvOp::StartClashService => { - let mut args = vec!["restart", self.tui_cfg.clash_srv_name.as_str()]; - if self.tui_cfg.is_user { - args.push("--user") - } - exec("systemctl", args)?; - exec( - "systemctl", - vec!["status", self.tui_cfg.clash_srv_name.as_str()], - ) - } - ClashSrvOp::StopClashService => { - let mut args = vec!["stop", self.tui_cfg.clash_srv_name.as_str()]; - if self.tui_cfg.is_user { - args.push("--user") - } - exec("systemctl", args)?; - exec( - "systemctl", - vec!["status", self.tui_cfg.clash_srv_name.as_str()], - ) - } - ClashSrvOp::SetPermission => ipc::exec_with_sbin( - "setcap", - vec![ - "'cap_net_admin,cap_net_bind_service=+ep'", - self.tui_cfg.clash_core_path.as_str(), - ], - ), - _ => Err(Error::new( - std::io::ErrorKind::NotFound, - "No Support Action", - )), - } - } - #[cfg(target_os = "windows")] pub fn clash_srv_ctl(&self, op: ClashSrvOp) -> Result { //let exe_dir = std::env::current_exe() // .unwrap() diff --git a/clashtui/src/utils/tui/impl_profile.rs b/clashtui/src/utils/tui/impl_profile.rs index 02c5397..8e1d2ff 100644 --- a/clashtui/src/utils/tui/impl_profile.rs +++ b/clashtui/src/utils/tui/impl_profile.rs @@ -1,7 +1,3 @@ -use std::os::unix::fs::{PermissionsExt, MetadataExt}; -use std::io::BufRead; -use regex::Regex; - use crate::utils::tui::{NetProviderMap, UpdateProviderType, ProfileType}; use api::ProfileTimeMap; @@ -271,10 +267,7 @@ impl ClashTuiUtil { self.tui_cfg.clash_cfg_dir, path, ); - #[cfg(target_os = "windows")] return exec("cmd", vec!["/C", cmd.as_str()]); - #[cfg(target_os = "linux")] - exec("sh", vec!["-c", cmd.as_str()]) } pub fn select_profile(&self, profile_name: &String) -> std::io::Result<()> { @@ -560,38 +553,6 @@ impl ClashTuiUtil { &None } - - // Check if need to correct perms of files in clash_cfg_dir. If perm is incorrect return false. - pub fn check_perms_of_ccd_files(&self) -> bool { - let dir = Path::new(self.tui_cfg.clash_cfg_dir.as_str()); - //let group_name = Utils::get_file_group_name(&dir.to_path_buf()); - //if group_name.is_none() { - // return false; - //} - - // check set-group-id - if let Ok(metadata) = std::fs::metadata(dir) { - let permissions = metadata.permissions(); - if permissions.mode() & 0o2000 == 0 { - return false; - } - } - - if let Ok(metadata) = std::fs::metadata(dir) { - if let Some(dir_group) = - nix::unistd::Group::from_gid(nix::unistd::Gid::from_raw(metadata.gid())).unwrap() - { - if Utils::find_files_not_in_group(&dir.to_path_buf(), dir_group.name.as_str()).len() > 0 - || Utils::find_files_not_group_writable(&dir.to_path_buf()).len() > 0 - { - return false; - } - } - } - - return true; - } - } impl ClashTuiUtil { @@ -659,6 +620,9 @@ impl ClashTuiUtil { } pub fn extract_profile_url(&self, profile_name: &str) -> std::io::Result { + use std::io::BufRead; + use regex::Regex; + let profile_path = self.profile_dir.join(profile_name); let file = File::open(profile_path)?; let reader = std::io::BufReader::new(file); @@ -693,10 +657,7 @@ impl ClashTuiUtil { #[allow(unused)] fn crt_symlink_file>(original: P, target: P) -> std::io::Result<()> { use std::os; - #[cfg(target_os = "windows")] return os::windows::fs::symlink_file(original, target); - #[cfg(target_os = "linux")] - os::unix::fs::symlink(original, target) } #[cfg(test)] diff --git a/clashtui/src/utils/utils.rs b/clashtui/src/utils/utils.rs index 5ba5f4b..d02cf04 100644 --- a/clashtui/src/utils/utils.rs +++ b/clashtui/src/utils/utils.rs @@ -1,10 +1,4 @@ -use std::{fs, process, env}; -use std::os::unix::fs::{PermissionsExt, MetadataExt}; -use nix::unistd::{Uid, Gid, Group, User, geteuid, setfsuid, setfsgid, getgroups, setgroups, initgroups, setuid, setgid}; use std::path::{Path, PathBuf}; -//use libc::{getlogin, setreuid, setuid, setgid}; -use std::ffi::{CStr, CString}; -use std::os::unix::process::CommandExt; pub(super) fn get_file_names

(dir: P) -> std::io::Result> where @@ -63,192 +57,4 @@ pub fn str_duration(t: std::time::Duration) -> String { let day = t.as_secs() / (3600 * 24); format!("{day}d") } -} - -pub fn modify_file_perms_in_dir(dir: &PathBuf, group_name: &str) { - let files_not_in_group = find_files_not_in_group(dir, group_name); - for file in &files_not_in_group { - let path = std::path::Path::new(dir).join(file); - if let Ok(group) = Group::from_name(group_name) { - println!("Changing group to '{}' for {:?}:", group_name, file); - if let Err(e) = nix::unistd::chown(&path, None, group.map(|g| g.gid)) { - eprintln!("Failed to change group to '{}' for '{:?}': {}", group_name, file, e); - } - } - } - - let files_not_group_writable = find_files_not_group_writable(dir); - for file in &files_not_group_writable { - if let Ok(metadata) = fs::metadata(file) { - let permissions = metadata.permissions(); - let mut new_permissions = permissions.clone(); - new_permissions.set_mode(permissions.mode() | 0o0020); - println!("Adding `g+w` permission to '{:?}'", file); - if let Err(e) = fs::set_permissions(file, new_permissions) { - eprintln!("Failed to set `g+w` permissions for '{:?}': {}", file, e); - } - } - } - - // dir add set-group-id: `chmod g+s dir` - if let Ok(metadata) = fs::metadata(dir) { - let permissions = metadata.permissions(); - let mut new_permissions = permissions.clone(); - new_permissions.set_mode(permissions.mode() | 0o2020); - println!("Adding `g+s` permission to '{:?}'", dir); - if let Err(e) = fs::set_permissions(dir, new_permissions) { - eprintln!("Failed to set `g+s` permissions for '{:?}': {}", dir, e); - } - } -} - -// Check dir member and dir itself. -pub fn find_files_not_group_writable(dir: &PathBuf) -> Vec { - let mut result = Vec::new(); - - if let Ok(entries) = fs::read_dir(dir) { - for entry in entries { - if let Ok(entry) = entry { - let path = entry.path(); - let metadata = entry.metadata().unwrap(); - if metadata.is_file() { - let permissions = metadata.permissions(); - - if permissions.mode() & 0o0020 == 0 { - result.push(path.clone()); - } - } - if metadata.is_dir() { - result.extend(find_files_not_group_writable(&path)); - } - } - } - } - - if let Ok(metadata) = fs::metadata(dir) { - let permissions = metadata.permissions(); - if permissions.mode() & 0o0020 == 0 { - result.push(dir.clone()); - } - } - - result -} - -// Check dir member and dir itself. -pub fn find_files_not_in_group(dir: &PathBuf, group_name: &str) -> Vec { - let mut result = Vec::new(); - - if let Ok(entries) = fs::read_dir(dir) { - for entry in entries { - if let Ok(entry) = entry { - let metadata = entry.metadata().unwrap(); - - if metadata.is_file() { - let file_gid = metadata.gid(); - if let Ok(Some(group)) = - Group::from_gid(Gid::from_raw(file_gid)) - { - if group.name != group_name { - result.push(entry.path().clone()); - } - } - } else if metadata.is_dir() { - let sub_dir = entry.path(); - result.extend(find_files_not_in_group(&sub_dir, group_name)); - } - } - } - } - - if let Ok(metadata) = fs::metadata(dir) { - if let Some(dir_group) = - Group::from_gid(Gid::from_raw(metadata.gid())).unwrap() - { - if dir_group.name != group_name { - result.push(dir.clone()); - } - } - } - - result -} - -pub fn get_file_group_name(dir: &PathBuf) -> Option { - if let Ok(metadata) = std::fs::metadata(dir) { - if let Some(dir_group) = - nix::unistd::Group::from_gid(nix::unistd::Gid::from_raw(metadata.gid())).unwrap() - { - return Some(dir_group.name); - } - } - - None -} - -// Perform file operations with clashtui process as a sudo user. -pub fn mock_fileop_as_sudo_user() { - if ! is_run_as_root() { - return; - } - - // sudo printenv: SUDO_USER, SUDO_UID, SUDO_GID, ... - if let (Ok(uid_str), Ok(gid_str)) = (env::var("SUDO_UID"), env::var("SUDO_GID")) { - if let (Ok(uid_num), Ok(gid_num)) = (uid_str.parse::(), gid_str.parse::()) { - // In Linux, file operation permissions are determined using fsuid, fdgid, and auxiliary groups. - - let uid = Uid::from_raw(uid_num); - let gid = Gid::from_raw(gid_num); - setfsuid(uid); - setfsgid(gid); - - // Need to use the group permissions of the auxiliary group mihomo - if let Ok(user_name) = env::var("SUDO_USER") { - let user_name = CString::new(user_name).unwrap(); - let _ = initgroups(&user_name, gid); - } - } - } -} - -pub fn is_run_as_root() -> bool { - return geteuid().is_root(); -} - -pub fn restore_fileop_as_root() { - setfsuid(Uid::from_raw(0)); - setfsgid(Gid::from_raw(0)); -} - -pub fn run_as_root() { - let app_path_binding = env::current_exe() - .expect("Failed to get current executable path"); - let app_path = app_path_binding.to_str() - .expect("Failed to convert path to string"); - - // Skip the param of exe path - let params: Vec = env::args().skip(1).collect(); - - let mut sudo_cmd = vec![app_path]; - - sudo_cmd.extend(params.iter().map(|s| s.as_str())); - - // CLASHTUI_EP: clashtui elevate privileges - env::set_var("CLASHTUI_EP", "true"); // To distinguish when users manually execute `sudo clashtui` - let _ = process::Command::new("sudo") - .args(vec!["--preserve-env=CLASHTUI_EP,XDG_CONFIG_HOME,HOME,USER"]) - //.args(vec!["--preserve-env"]) - .args(&sudo_cmd) - .exec(); -} - -// Is clashtui elevate privileges -pub fn is_clashtui_ep() -> bool { - if let Ok(str) = env::var("CLASHTUI_EP") { - if str == "true" { - return true; - } - } - - false -} +} \ No newline at end of file diff --git a/clashtui/ui-derive/Cargo.toml b/clashtui/ui-derive/Cargo.toml index 67a1101..baec77a 100644 --- a/clashtui/ui-derive/Cargo.toml +++ b/clashtui/ui-derive/Cargo.toml @@ -8,5 +8,5 @@ edition = "2021" proc-macro = true [dependencies] -syn = {version = "2.0.52", features = ["default"]} +syn = {version = "2.0.53", features = ["default"]} quote = "*"