Skip to main content

ib_hook/process/
mod.rs

1/*!
2Process utilities.
3*/
4#[cfg(feature = "sysinfo")]
5use std::path::PathBuf;
6#[cfg(feature = "std")]
7use std::{
8    mem::{MaybeUninit, transmute},
9    time::SystemTime,
10};
11
12use derive_more::{Deref, Display};
13use windows::Win32::{
14    Foundation::{GetLastError, HWND, WIN32_ERROR},
15    System::Threading::{
16        GetProcessIdOfThread, GetProcessTimes, OpenProcess, OpenThread,
17        PROCESS_QUERY_LIMITED_INFORMATION, THREAD_QUERY_LIMITED_INFORMATION,
18    },
19    UI::WindowsAndMessaging::GetWindowThreadProcessId,
20};
21
22use crate::log::*;
23
24#[cfg(feature = "std")]
25mod gui;
26pub mod module;
27
28#[cfg(feature = "std")]
29pub use gui::*;
30
31/// Process ID.
32#[derive(Clone, Copy, PartialEq, Eq, Hash, Display, Debug, Deref)]
33#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
34pub struct Pid(pub u32);
35
36impl Pid {
37    #[cfg(feature = "std")]
38    pub fn current() -> Self {
39        Self(std::process::id())
40    }
41
42    pub fn from_tid(tid: u32) -> windows::core::Result<Self> {
43        let thread = unsafe { OpenThread(THREAD_QUERY_LIMITED_INFORMATION, false, tid) }?;
44        match unsafe { GetProcessIdOfThread(thread) } {
45            0 => Err(windows::core::Error::from_thread()),
46            pid => Ok(Pid(pid)),
47        }
48    }
49
50    fn from_hwnd_with_thread(hwnd: HWND) -> Result<(Self, u32), WIN32_ERROR> {
51        let mut pid: u32 = 0;
52        let tid = unsafe { GetWindowThreadProcessId(hwnd, Some(&mut pid)) };
53        if tid != 0 {
54            Ok((Pid(pid), tid))
55        } else {
56            Err(unsafe { GetLastError() })
57        }
58    }
59
60    /// Gets the process ID from a window handle.
61    ///
62    /// This uses [`GetWindowThreadProcessId`] to retrieve the PID associated with a window.
63    pub fn from_hwnd(hwnd: HWND) -> Result<Self, WIN32_ERROR> {
64        Self::try_from(hwnd)
65    }
66}
67
68impl TryFrom<HWND> for Pid {
69    type Error = WIN32_ERROR;
70
71    /// Gets the process ID from a window handle.
72    ///
73    /// This uses [`GetWindowThreadProcessId`] to retrieve the PID associated with a window.
74    fn try_from(hwnd: HWND) -> Result<Self, Self::Error> {
75        Self::from_hwnd_with_thread(hwnd).map(|(pid, _tid)| pid)
76    }
77}
78
79#[cfg(feature = "std")]
80impl Pid {
81    /// Gets the start time of the process.
82    pub fn get_start_time(self) -> windows::core::Result<SystemTime> {
83        let pid = self.0;
84        let handle = unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid) }?;
85
86        // let mut start: nt_time::FileTime = Default::default();
87        let mut start: MaybeUninit<SystemTime> = MaybeUninit::uninit();
88        let mut x = Default::default();
89        unsafe { GetProcessTimes(handle, &mut start as *mut _ as _, &mut x, &mut x, &mut x) }?;
90
91        Ok(unsafe { start.assume_init() })
92    }
93
94    /// Gets the start time of the process.
95    ///
96    /// [`#![feature(time_systemtime_limits)]`](https://github.com/rust-lang/rust/issues/149067) is not stable yet.
97    pub fn get_start_time_or_max(self) -> SystemTime {
98        self.get_start_time()
99            .inspect_err(|e| debug!(%e, "get_start_time"))
100            .unwrap_or_else(|_| {
101                // https://github.com/sorairolake/nt-time/issues/467
102                // nt_time::FileTime::MAX.into()
103                unsafe { transmute(i64::MAX) }
104            })
105    }
106}
107
108#[cfg(feature = "sysinfo")]
109impl Pid {
110    pub fn with_process<R>(
111        self,
112        refresh_info: sysinfo::ProcessRefreshKind,
113        f: impl FnOnce(&sysinfo::Process) -> R,
114    ) -> Option<R> {
115        let pid = self.clone().into();
116        let mut system = sysinfo::System::new();
117        system.refresh_processes_specifics(
118            sysinfo::ProcessesToUpdate::Some(&[pid]),
119            false,
120            refresh_info,
121        );
122        system.process(pid).map(f)
123    }
124
125    pub fn image_path(self) -> Option<PathBuf> {
126        self.with_process(
127            sysinfo::ProcessRefreshKind::nothing().with_exe(sysinfo::UpdateKind::Always),
128            |p| p.exe().map(|p| p.to_owned()),
129        )
130        .flatten()
131    }
132}
133
134#[cfg(feature = "sysinfo")]
135impl From<sysinfo::Pid> for Pid {
136    fn from(pid: sysinfo::Pid) -> Self {
137        Self(pid.as_u32())
138    }
139}
140
141#[cfg(feature = "sysinfo")]
142impl Into<sysinfo::Pid> for Pid {
143    fn into(self) -> sysinfo::Pid {
144        sysinfo::Pid::from_u32(self.0)
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151
152    use windows::Win32::UI::WindowsAndMessaging::GetDesktopWindow;
153
154    #[test]
155    fn pid_from_hwnd() {
156        // Test with a simple window - use GetDesktopWindow as it always exists
157        let desktop = unsafe { GetDesktopWindow() };
158        let pid = Pid::from_hwnd(desktop);
159
160        // Desktop window should have a valid PID (usually 0 or the session manager)
161        assert!(pid.is_ok(), "Should be able to get PID from desktop window");
162        println!("Desktop window PID: {:?}", pid.unwrap());
163    }
164
165    #[test]
166    #[cfg(feature = "sysinfo")]
167    fn pid_from_hwnd_desktop() {
168        let desktop = unsafe { GetDesktopWindow() };
169        dbg!(desktop);
170        let pid = Pid::from_hwnd(desktop).expect("Should get PID from desktop window");
171        dbg!(pid);
172
173        let exe_path = pid.image_path();
174        // Should be C:\Windows\System32\csrss.exe if permission enough
175        assert_eq!(exe_path, None);
176    }
177
178    /// Test with GetDesktopWindow and verify the process is explorer.exe
179    #[test]
180    #[cfg(feature = "sysinfo")]
181    fn pid_from_hwnd_shell() {
182        use std::path::Path;
183        use windows::Win32::UI::WindowsAndMessaging::GetShellWindow;
184
185        let desktop = unsafe { GetShellWindow() };
186        dbg!(desktop);
187        let pid = Pid::from_hwnd(desktop).expect("Should get PID from desktop window");
188        dbg!(pid);
189
190        // The desktop window is typically owned by explorer.exe
191        let exe_path = pid.image_path().unwrap();
192        assert_eq!(exe_path, Path::new(r"C:\Windows\explorer.exe"));
193    }
194
195    #[test]
196    fn get_start_time() {
197        let current_pid = std::process::id();
198        let pid = Pid(current_pid);
199
200        let start_time = pid.get_start_time().unwrap();
201        dbg!(start_time);
202        let t = start_time
203            .duration_since(SystemTime::UNIX_EPOCH)
204            .unwrap()
205            .as_secs();
206        assert!(t > 0, "{t}");
207
208        let start_time2 = pid.get_start_time().unwrap();
209        dbg!(start_time2);
210        assert_eq!(start_time, start_time2);
211    }
212}