use crate::process::config::{DCMode, FilterCriterion, FilterRuleType, ProcessConfig, WindowFilter};
use crate::process::state::ProcessState;
use windows::Win32::Foundation::{HANDLE, HWND};
use windows::Win32::Graphics::Gdi::HDC;
use crate::{
get_window_client_dc, get_window_dc, handle::open_process_rw_handle, hdc::get_desktop_dc,
snapshot::get_process_pid,
};
#[derive(Debug, Clone)]
pub enum ProcessError {
ProcessNotFound(String),
HandleOpenFailed(u32),
WindowNotFound(u32),
DCNotFound(HWND),
DuplicateName(String),
ProcessNotRegistered(String),
LockPoisoned,
}
impl std::fmt::Display for ProcessError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ProcessError::ProcessNotFound(name) => write!(f, "Process '{}' not found", name),
ProcessError::HandleOpenFailed(pid) => {
write!(f, "Failed to open handle for PID {}", pid)
}
ProcessError::WindowNotFound(pid) => write!(f, "No window found for PID {}", pid),
ProcessError::DCNotFound(hwnd) => write!(f, "Failed to get DC for window {:?}", hwnd),
ProcessError::DuplicateName(name) => {
write!(f, "Process '{}' is already registered", name)
}
ProcessError::ProcessNotRegistered(name) => {
write!(f, "Process '{}' is not registered", name)
}
ProcessError::LockPoisoned => {
write!(
f,
"RwLock poisoned (a thread panicked while holding the lock)"
)
}
}
}
}
impl std::error::Error for ProcessError {}
pub type ProcessResult<T> = Result<T, ProcessError>;
#[derive(Debug)]
pub struct Process {
config: ProcessConfig,
state: Option<ProcessState>,
}
unsafe impl Send for Process {}
unsafe impl Sync for Process {}
impl Process {
pub fn new(config: ProcessConfig) -> Self {
Self {
config,
state: None,
}
}
pub fn by_name(process_name: &str) -> Self {
let config = ProcessConfig::new(process_name);
Self::new(config)
}
pub fn init_by_name(process_name: &str) -> ProcessResult<Self> {
let mut process = Self::by_name(process_name);
process.init()?;
Ok(process)
}
pub fn init_by_pid(pid: u32) -> ProcessResult<Self> {
let config = ProcessConfig::new("");
let mut process = Self::new(config);
process.init_with_pid(pid)?;
Ok(process)
}
pub fn config(&self) -> &ProcessConfig {
&self.config
}
pub fn dc_mode(&self) -> DCMode {
self.config.dc_mode
}
pub fn pid(&self) -> Option<u32> {
self.state.as_ref().map(|s| s.pid)
}
pub fn hwnd(&self) -> Option<HWND> {
self.state.as_ref().and_then(|s| s.hwnd)
}
pub fn handle(&self) -> Option<HANDLE> {
self.state.as_ref().and_then(|s| s.handle)
}
pub fn hdc(&self) -> Option<HDC> {
self.state.as_ref().and_then(|s| s.hdc)
}
pub fn hwnd_or_default(&self) -> HWND {
self.hwnd().unwrap_or_default()
}
pub fn handle_or_default(&self) -> HANDLE {
self.handle().unwrap_or_default()
}
pub fn hdc_or_default(&self) -> HDC {
self.hdc().unwrap_or_default()
}
pub fn pid_or_default(&self) -> u32 {
self.pid().unwrap_or(0)
}
pub fn is_valid(&self) -> bool {
self.state.as_ref().map_or(false, |s| s.is_valid())
}
pub fn state(&self) -> Option<&ProcessState> {
self.state.as_ref()
}
pub fn init(&mut self) -> ProcessResult<()> {
if self.state.is_some() {
self.cleanup()?;
}
let (pid, hwnd) = self.find_process()?;
let handle = if self.config.init_flags.init_handle {
Some(open_process_rw_handle(pid).ok_or_else(|| ProcessError::HandleOpenFailed(pid))?)
} else {
None
};
let hdc = if self.config.init_flags.init_dc && self.config.init_flags.init_hwnd {
Some(self.create_dc(hwnd)?)
} else {
None
};
let hwnd_opt = if self.config.init_flags.init_hwnd {
Some(hwnd)
} else {
None
};
self.state = Some(ProcessState {
pid,
hwnd: hwnd_opt,
handle,
hdc,
});
Ok(())
}
pub fn reinit(&mut self) -> ProcessResult<()> {
self.init()
}
pub fn init_with_pid(&mut self, pid: u32) -> ProcessResult<()> {
if self.state.is_some() {
self.cleanup()?;
}
let hwnd = if self.config.init_flags.init_hwnd || self.config.init_flags.init_dc {
Some(self.find_window_for_pid(pid)?)
} else {
None
};
let handle = if self.config.init_flags.init_handle {
Some(open_process_rw_handle(pid).ok_or_else(|| ProcessError::HandleOpenFailed(pid))?)
} else {
None
};
let hdc = if self.config.init_flags.init_dc && hwnd.is_some() {
Some(self.create_dc(hwnd.unwrap())?)
} else {
None
};
self.state = Some(ProcessState {
pid,
hwnd,
handle,
hdc,
});
Ok(())
}
pub fn cleanup(&mut self) -> ProcessResult<()> {
if let Some(state) = self.state.take() {
drop(state); }
Ok(())
}
pub fn reset(&mut self) {
self.state = None;
}
fn find_process(&self) -> ProcessResult<(u32, HWND)> {
let process_name = &self.config.process_name;
if let Some(filter) = &self.config.window_filter {
self.find_by_name_with_filter(process_name, filter)
} else {
self.find_by_name(process_name)
}
}
fn find_by_name(&self, process_name: &str) -> ProcessResult<(u32, HWND)> {
let pid = get_process_pid(process_name)
.ok_or_else(|| ProcessError::ProcessNotFound(process_name.to_string()))?;
let hwnd = self.find_window_for_pid(pid)?;
Ok((pid, hwnd))
}
fn find_by_name_with_filter(
&self,
process_name: &str,
filter: &WindowFilter,
) -> ProcessResult<(u32, HWND)> {
use crate::hwnd::get_hwnd_list_by_pid;
use crate::snapshot::find_pids_by_name;
if filter.rules.is_empty() {
return self.find_by_name(process_name);
}
let all_pids = find_pids_by_name(process_name);
if all_pids.is_empty() {
return Err(ProcessError::ProcessNotFound(process_name.to_string()));
}
for pid in &all_pids {
let all_windows = get_hwnd_list_by_pid(*pid);
for hwnd in &all_windows {
if self.matches_filter(*hwnd, filter) {
return Ok((*pid, *hwnd));
}
}
}
Err(ProcessError::WindowNotFound(0))
}
fn matches_filter(&self, hwnd: HWND, filter: &WindowFilter) -> bool {
use windows::Win32::UI::WindowsAndMessaging::{GetWindowTextW, IsWindowVisible};
if filter.rules.is_empty() {
return true;
}
let get_title = |hwnd: HWND| -> String {
let mut buffer = [0u16; 256];
let length = unsafe { GetWindowTextW(hwnd, &mut buffer) };
if length == 0 {
return String::new();
}
String::from_utf16(&buffer[..length as usize]).unwrap_or_default()
};
let title = get_title(hwnd);
let is_visible = unsafe { IsWindowVisible(hwnd).as_bool() };
for rule in &filter.rules {
let matches = match &rule.criterion {
FilterCriterion::ByWindowTitle {
title_pattern,
case_sensitive,
} => {
if *case_sensitive {
title == *title_pattern
} else {
title.to_lowercase().contains(&title_pattern.to_lowercase())
}
}
FilterCriterion::ByVisibility(visible) => is_visible == *visible,
};
match rule.rule_type {
FilterRuleType::Include => {
if !matches {
return false; }
}
FilterRuleType::Exclude => {
if matches {
return false; }
}
}
}
true }
fn find_window_for_pid(&self, pid: u32) -> ProcessResult<HWND> {
crate::hwnd::get_hwnd_by_pid(pid).ok_or_else(|| ProcessError::WindowNotFound(pid))
}
fn create_dc(&self, hwnd: HWND) -> ProcessResult<HDC> {
match self.config.dc_mode {
DCMode::Desktop => {
get_desktop_dc().ok_or_else(|| ProcessError::DCNotFound(HWND::default()))
}
DCMode::Standard => get_window_dc(hwnd).ok_or_else(|| ProcessError::DCNotFound(hwnd)),
DCMode::WindowClient => {
get_window_client_dc(hwnd).ok_or_else(|| ProcessError::DCNotFound(hwnd))
}
}
}
}
impl Drop for Process {
fn drop(&mut self) {
let _ = self.cleanup();
}
}