diff --git a/src/processes/linux_list.rs b/src/processes/linux_list.rs deleted file mode 100644 index 170ab80a..00000000 --- a/src/processes/linux_list.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::processes::{ProcessInfo, ProcessList}; -use anyhow::Result; -use std::collections::hash_map::Entry; -use std::collections::HashMap; -use std::path::PathBuf; -use sysinfo::{ProcessRefreshKind, ProcessesToUpdate, System, UpdateKind}; - -pub fn active_executables() -> Result { - let mut executables: HashMap = HashMap::new(); - let mut sys = System::new(); - - sys.refresh_processes_specifics( - ProcessesToUpdate::All, - true, - ProcessRefreshKind::nothing().with_exe(UpdateKind::OnlyIfNotSet), - ); - - for process in sys.processes().values() { - // process.exe() will return an empty path if there was an error while trying to read /proc//exe. - if let Some(path) = process.exe() { - let executable = path.to_path_buf(); - - match executables.entry(executable) { - Entry::Occupied(_) => {} - Entry::Vacant(e) => { - let executable = e.key().clone(); - // .file_name() returns `None` if the path terminates in `..` - // We use the absolute path in such a case. - let display_name = match path.file_name() { - Some(s) => s.to_string_lossy().to_string(), - None => path.to_string_lossy().to_string(), - }; - e.insert(ProcessInfo { - executable, - display_name, - is_visible: false, - is_system: false, - }); - } - } - } - } - Ok(executables.into_values().collect()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn process_list() { - let lst = active_executables().unwrap(); - assert!(!lst.is_empty()); - } -} diff --git a/src/processes/macos_list.rs b/src/processes/macos_list.rs deleted file mode 100644 index eac64c17..00000000 --- a/src/processes/macos_list.rs +++ /dev/null @@ -1,122 +0,0 @@ -use crate::intercept_conf::PID; -use crate::processes::{ProcessInfo, ProcessList}; -use anyhow::Result; -use cocoa::base::nil; -use cocoa::foundation::NSString; -use core_foundation::number::kCFNumberSInt32Type; -use core_foundation::number::CFNumberGetValue; -use core_foundation::number::CFNumberRef; -use core_graphics::display::{ - kCGNullWindowID, kCGWindowListExcludeDesktopElements, kCGWindowListOptionOnScreenOnly, - CFArrayGetCount, CFArrayGetValueAtIndex, CFDictionaryGetValueIfPresent, CFDictionaryRef, - CGWindowListCopyWindowInfo, -}; -use std::collections::hash_map::Entry; -use std::collections::{HashMap, HashSet}; -use std::ffi::c_void; -use std::path::PathBuf; -use sysinfo::{ProcessRefreshKind, ProcessesToUpdate, System, UpdateKind}; - -pub fn active_executables() -> Result { - let mut executables: HashMap = HashMap::new(); - let visible = visible_windows()?; - let mut sys = System::new(); - sys.refresh_processes_specifics( - ProcessesToUpdate::All, - true, - ProcessRefreshKind::nothing().with_exe(UpdateKind::OnlyIfNotSet), - ); - for (pid, process) in sys.processes() { - // process.exe() will return empty path if there was an error while trying to read /proc//exe. - if let Some(path) = process.exe() { - let pid = pid.as_u32(); - let executable = path.to_path_buf(); - match executables.entry(executable) { - Entry::Occupied(mut e) => { - let process_info = e.get(); - if !process_info.is_visible && visible.contains(&pid) { - e.get_mut().is_visible = true; - } - } - Entry::Vacant(e) => { - let executable = e.key().clone(); - // .file_name() returns `None` if the path terminates in `..` - // We use the absolute path in such a case. - let display_name = match path.file_name() { - Some(s) => s.to_string_lossy().to_string(), - None => path.to_string_lossy().to_string(), - }; - let is_visible = visible.contains(&pid); - let is_system = executable.starts_with("/System/"); - e.insert(ProcessInfo { - executable, - display_name, - is_visible, - is_system, - }); - } - } - } - } - Ok(executables.into_values().collect()) -} - -pub fn visible_windows() -> Result> { - let mut pids: HashSet = HashSet::new(); - unsafe { - let windows_info_list = CGWindowListCopyWindowInfo( - kCGWindowListOptionOnScreenOnly + kCGWindowListExcludeDesktopElements, - kCGNullWindowID, - ); - let count = CFArrayGetCount(windows_info_list); - - for i in 0..count - 1 { - let dic_ref = CFArrayGetValueAtIndex(windows_info_list, i); - let key = NSString::alloc(nil).init_str("kCGWindowOwnerPID"); - let mut pid: *const c_void = std::ptr::null_mut(); - - if CFDictionaryGetValueIfPresent( - dic_ref as CFDictionaryRef, - key as *const c_void, - &mut pid, - ) != 0 - { - let pid_cf_ref = pid as CFNumberRef; - let mut pid: i32 = 0; - if CFNumberGetValue( - pid_cf_ref, - kCFNumberSInt32Type, - &mut pid as *mut i32 as *mut c_void, - ) { - pids.insert(pid as u32); - } - } - } - Ok(pids) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn process_list() { - let lst = active_executables().unwrap(); - assert!(!lst.is_empty()); - - for proc in &lst { - if !proc.is_visible { - dbg!(&proc.display_name); - } - } - dbg!(lst.len()); - } - - #[test] - fn visible_windows_list() { - let open_windows_pids = visible_windows().unwrap(); - assert!(!open_windows_pids.is_empty()); - dbg!(open_windows_pids.len()); - } -} diff --git a/src/processes/mod.rs b/src/processes/mod.rs index ec8505b3..4ba0794c 100644 --- a/src/processes/mod.rs +++ b/src/processes/mod.rs @@ -1,10 +1,10 @@ pub use image; use std::path::PathBuf; -#[cfg(target_os = "macos")] -mod macos_list; -#[cfg(target_os = "macos")] -pub use self::macos_list::active_executables; +#[cfg(any(target_os = "linux", target_os = "macos"))] +mod nix_list; +#[cfg(any(target_os = "linux", target_os = "macos"))] +pub use self::nix_list::active_executables; #[cfg(windows)] mod windows_list; @@ -13,11 +13,6 @@ pub use self::windows_list::active_executables; #[cfg(windows)] pub use self::windows_list::get_process_name; -#[cfg(target_os = "linux")] -mod linux_list; -#[cfg(target_os = "linux")] -pub use self::linux_list::active_executables; - #[cfg(target_os = "macos")] mod macos_icons; #[cfg(target_os = "macos")] @@ -44,7 +39,7 @@ pub static ICON_CACHE: once_cell::sync::Lazy> = pub mod bench { #[cfg(target_os = "macos")] - pub use super::macos_list::visible_windows; + pub use super::nix_list::visible_windows; #[cfg(windows)] pub use super::windows_list::visible_windows; diff --git a/src/processes/nix_list.rs b/src/processes/nix_list.rs new file mode 100644 index 00000000..543fa99b --- /dev/null +++ b/src/processes/nix_list.rs @@ -0,0 +1,161 @@ +use crate::intercept_conf::PID; +use crate::processes::{ProcessInfo, ProcessList}; +use anyhow::Result; +use std::collections::hash_map::Entry; +use std::collections::{HashMap, HashSet}; +use std::path::PathBuf; +use sysinfo::{Process, ProcessRefreshKind, ProcessesToUpdate, System, UpdateKind}; + +#[cfg(target_os = "linux")] +use std::ops::Deref; + +#[cfg(target_os = "macos")] +use macos_visible_windows::macos_visible_windows; + +pub fn active_executables() -> Result { + let mut executables: HashMap = HashMap::new(); + let visible = visible_windows()?; + let mut sys = System::new(); + sys.refresh_processes_specifics( + ProcessesToUpdate::All, + true, + ProcessRefreshKind::nothing() + .with_exe(UpdateKind::OnlyIfNotSet) + .with_user(UpdateKind::OnlyIfNotSet), + ); + for (pid, process) in sys.processes() { + // process.exe() will return empty path if there was an error while trying to read /proc//exe. + if let Some(path) = process.exe() { + let pid = pid.as_u32(); + let executable = path.to_path_buf(); + match executables.entry(executable) { + Entry::Occupied(mut e) => { + let process_info = e.get(); + if !process_info.is_visible && visible.contains(&pid) { + e.get_mut().is_visible = true; + } + } + Entry::Vacant(e) => { + let executable = e.key().clone(); + // .file_name() returns `None` if the path terminates in `..` + // We use the absolute path in such a case. + let display_name = path + .file_name() + .unwrap_or(path.as_os_str()) + .to_string_lossy() + .to_string(); + let is_system = is_system(process); + let is_visible = visible.contains(&pid); + e.insert(ProcessInfo { + executable, + display_name, + is_visible, + is_system, + }); + } + } + } + } + Ok(executables.into_values().collect()) +} + +pub fn visible_windows() -> Result> { + #[cfg(target_os = "macos")] + return macos_visible_windows(); + + #[cfg(target_os = "linux")] + // Finding visible windows is less useful on Linux, where more applications tend to be CLI-based. + // So we skip all the X11/Wayland complexity. + return Ok(HashSet::new()); +} + +fn is_system(process: &Process) -> bool { + #[cfg(target_os = "macos")] + return process + .exe() + .map(|path| path.starts_with("/System/")) + .unwrap_or(false); + + #[cfg(target_os = "linux")] + return process + .user_id() + .map(|uid| *uid.deref() < 1000) + .unwrap_or(false); +} + +#[cfg(target_os = "macos")] +mod macos_visible_windows { + use crate::intercept_conf::PID; + use anyhow::Result; + use cocoa::base::nil; + use cocoa::foundation::NSString; + use core_foundation::number::{kCFNumberSInt32Type, CFNumberGetValue, CFNumberRef}; + use core_graphics::display::{ + kCGNullWindowID, kCGWindowListExcludeDesktopElements, kCGWindowListOptionOnScreenOnly, + CFArrayGetCount, CFArrayGetValueAtIndex, CFDictionaryGetValueIfPresent, CFDictionaryRef, + CGWindowListCopyWindowInfo, + }; + use std::collections::HashSet; + use std::ffi::c_void; + + pub fn macos_visible_windows() -> Result> { + let mut pids: HashSet = HashSet::new(); + unsafe { + let windows_info_list = CGWindowListCopyWindowInfo( + kCGWindowListOptionOnScreenOnly + kCGWindowListExcludeDesktopElements, + kCGNullWindowID, + ); + let count = CFArrayGetCount(windows_info_list); + + for i in 0..count - 1 { + let dic_ref = CFArrayGetValueAtIndex(windows_info_list, i); + let key = NSString::alloc(nil).init_str("kCGWindowOwnerPID"); + let mut pid: *const c_void = std::ptr::null_mut(); + + if CFDictionaryGetValueIfPresent( + dic_ref as CFDictionaryRef, + key as *const c_void, + &mut pid, + ) != 0 + { + let pid_cf_ref = pid as CFNumberRef; + let mut pid: i32 = 0; + if CFNumberGetValue( + pid_cf_ref, + kCFNumberSInt32Type, + &mut pid as *mut i32 as *mut c_void, + ) { + pids.insert(pid as u32); + } + } + } + Ok(pids) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn process_list() { + let lst = active_executables().unwrap(); + assert!(!lst.is_empty()); + + for proc in &lst { + if !proc.is_visible { + dbg!(&proc.display_name); + } + } + dbg!(lst.len()); + } + + #[cfg(target_os = "macos")] + #[test] + fn visible_windows_list() { + let open_windows_pids = visible_windows().unwrap(); + assert!(!open_windows_pids.is_empty()); + dbg!(open_windows_pids.len()); + } +}