2025-11-07 18:02:30 +02:00
|
|
|
use std::fmt;
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub struct ProcessInfo {
|
|
|
|
|
pub pid: u32,
|
2025-11-07 18:07:07 +02:00
|
|
|
pub ppid: u32,
|
2025-11-07 18:02:30 +02:00
|
|
|
pub name: String,
|
|
|
|
|
pub path: Option<String>,
|
2025-11-07 18:07:07 +02:00
|
|
|
pub thread_count: u32,
|
2025-11-07 18:02:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl fmt::Display for ProcessInfo {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
|
write!(f, "[{}] {}", self.pid, self.name)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(windows)]
|
|
|
|
|
mod platform {
|
|
|
|
|
use super::ProcessInfo;
|
|
|
|
|
use anyhow::{Context, Result};
|
|
|
|
|
use windows::Win32::Foundation::{CloseHandle, HANDLE};
|
|
|
|
|
use windows::Win32::System::Diagnostics::ToolHelp::{
|
|
|
|
|
CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, PROCESSENTRY32W,
|
|
|
|
|
TH32CS_SNAPPROCESS,
|
|
|
|
|
};
|
2025-11-08 11:08:21 +02:00
|
|
|
use windows::Win32::System::Threading::{OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION};
|
|
|
|
|
use windows::Win32::System::ProcessStatus::GetProcessImageFileNameW;
|
2025-11-07 18:02:30 +02:00
|
|
|
|
|
|
|
|
pub fn enumerate_processes() -> Result<Vec<ProcessInfo>> {
|
|
|
|
|
let mut processes = Vec::new();
|
|
|
|
|
|
|
|
|
|
unsafe {
|
|
|
|
|
let snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
|
|
|
|
|
.context("Failed to create process snapshot")?;
|
|
|
|
|
|
|
|
|
|
let mut entry = PROCESSENTRY32W {
|
|
|
|
|
dwSize: std::mem::size_of::<PROCESSENTRY32W>() as u32,
|
|
|
|
|
..Default::default()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if Process32FirstW(snapshot, &mut entry).is_ok() {
|
|
|
|
|
loop {
|
|
|
|
|
let name = String::from_utf16_lossy(
|
|
|
|
|
&entry.szExeFile[..entry
|
|
|
|
|
.szExeFile
|
|
|
|
|
.iter()
|
|
|
|
|
.position(|&c| c == 0)
|
|
|
|
|
.unwrap_or(entry.szExeFile.len())],
|
|
|
|
|
);
|
|
|
|
|
|
2025-11-08 11:08:21 +02:00
|
|
|
// Try to get full process path
|
|
|
|
|
let path = get_process_path(entry.th32ProcessID);
|
|
|
|
|
|
2025-11-07 18:02:30 +02:00
|
|
|
processes.push(ProcessInfo {
|
|
|
|
|
pid: entry.th32ProcessID,
|
2025-11-07 18:07:07 +02:00
|
|
|
ppid: entry.th32ParentProcessID,
|
2025-11-07 18:02:30 +02:00
|
|
|
name,
|
2025-11-08 11:08:21 +02:00
|
|
|
path,
|
2025-11-07 18:07:07 +02:00
|
|
|
thread_count: entry.cntThreads,
|
2025-11-07 18:02:30 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if Process32NextW(snapshot, &mut entry).is_err() {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let _ = CloseHandle(snapshot);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(processes)
|
|
|
|
|
}
|
2025-11-08 11:08:21 +02:00
|
|
|
|
|
|
|
|
fn get_process_path(pid: u32) -> Option<String> {
|
|
|
|
|
unsafe {
|
|
|
|
|
let handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid).ok()?;
|
|
|
|
|
let mut buffer = [0u16; 1024];
|
|
|
|
|
|
|
|
|
|
if GetProcessImageFileNameW(handle, &mut buffer) > 0 {
|
|
|
|
|
let _ = CloseHandle(handle);
|
|
|
|
|
let path = String::from_utf16_lossy(
|
|
|
|
|
&buffer[..buffer.iter().position(|&c| c == 0).unwrap_or(buffer.len())],
|
|
|
|
|
);
|
|
|
|
|
Some(path)
|
|
|
|
|
} else {
|
|
|
|
|
let _ = CloseHandle(handle);
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-11-07 18:02:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(not(windows))]
|
|
|
|
|
mod platform {
|
|
|
|
|
use super::ProcessInfo;
|
|
|
|
|
use anyhow::Result;
|
|
|
|
|
|
|
|
|
|
pub fn enumerate_processes() -> Result<Vec<ProcessInfo>> {
|
|
|
|
|
// TODO: Implement Linux/macOS enumeration
|
|
|
|
|
Ok(Vec::new())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn enumerate_processes() -> anyhow::Result<Vec<ProcessInfo>> {
|
|
|
|
|
platform::enumerate_processes()
|
|
|
|
|
}
|