1use std::ffi::c_void;
4use std::io;
5use std::mem;
6
7use ntapi::ntpsapi::{
8 NtSetInformationProcess,
9 ProcessIoPriority,
10};
11use num_enum::{
12 IntoPrimitive,
13 TryFromPrimitive,
14};
15use windows::Wdk::System::Threading::{
16 NtQueryInformationProcess,
17 PROCESSINFOCLASS,
18};
19use windows::Win32::Foundation::{
20 HANDLE,
21 HMODULE,
22};
23use windows::Win32::System::Diagnostics::ToolHelp::{
24 CreateToolhelp32Snapshot,
25 Thread32First,
26 Thread32Next,
27 TH32CS_SNAPTHREAD,
28 THREADENTRY32,
29};
30use windows::Win32::System::LibraryLoader::GetModuleHandleExW;
31use windows::Win32::System::Threading;
32use windows::Win32::System::Threading::{
33 GetCurrentProcess,
34 GetCurrentProcessId,
35 GetCurrentThread,
36 GetCurrentThreadId,
37 GetProcessId,
38 GetThreadId,
39 OpenProcess,
40 OpenThread,
41 SetPriorityClass,
42 SetThreadPriority,
43 PROCESS_ALL_ACCESS,
44 PROCESS_CREATION_FLAGS,
45 PROCESS_MODE_BACKGROUND_BEGIN,
46 PROCESS_MODE_BACKGROUND_END,
47 THREAD_ALL_ACCESS,
48 THREAD_MODE_BACKGROUND_BEGIN,
49 THREAD_MODE_BACKGROUND_END,
50 THREAD_PRIORITY,
51};
52
53use crate::internal::{
54 custom_err_with_code,
55 AutoClose,
56 ReturnValue,
57};
58
59pub struct Process {
61 handle: AutoClose<HANDLE>,
62}
63
64impl Process {
65 pub fn current() -> Self {
69 let pseudo_handle = unsafe { GetCurrentProcess() };
70 Self::from_maybe_null(pseudo_handle).expect("Pseudo process handle should never be null")
71 }
72
73 pub fn from_id<I>(id: I) -> io::Result<Self>
77 where
78 I: Into<ProcessId>,
79 {
80 let raw_handle = unsafe { OpenProcess(PROCESS_ALL_ACCESS, false, id.into().0)? };
81 Ok(Self {
82 handle: raw_handle.into(),
83 })
84 }
85
86 pub fn begin_background_mode() -> io::Result<()> {
90 unsafe { SetPriorityClass(Self::current().handle.entity, PROCESS_MODE_BACKGROUND_BEGIN)? };
91 Ok(())
92 }
93
94 pub fn end_background_mode() -> io::Result<()> {
96 unsafe { SetPriorityClass(Self::current().handle.entity, PROCESS_MODE_BACKGROUND_END)? };
97 Ok(())
98 }
99
100 pub fn set_priority(&mut self, priority: ProcessPriority) -> io::Result<()> {
112 unsafe { SetPriorityClass(self.handle.entity, priority.into())? };
113 Ok(())
114 }
115
116 pub fn get_io_priority(&self) -> io::Result<Option<IoPriority>> {
120 let mut raw_io_priority: i32 = 0;
121 let mut return_length: u32 = 0;
122 let ret_val = unsafe {
123 NtQueryInformationProcess(
124 self.handle.entity,
125 ProcessInformationClass::ProcessIoPriority.into(),
126 &mut raw_io_priority as *mut i32 as *mut c_void,
127 mem::size_of::<i32>() as u32,
128 &mut return_length,
129 )
130 };
131 ret_val.0.if_non_null_to_error(|| {
132 custom_err_with_code("Getting IO priority failed", ret_val.0)
133 })?;
134 Ok(IoPriority::try_from(raw_io_priority as u32).ok())
135 }
136
137 pub fn set_io_priority(&mut self, io_priority: IoPriority) -> io::Result<()> {
138 let ret_val = unsafe {
139 NtSetInformationProcess(
140 self.handle.entity.0 as *mut ntapi::winapi::ctypes::c_void,
141 i32::from(ProcessInformationClass::ProcessIoPriority)
142 .try_into()
143 .unwrap(),
144 &mut u32::from(io_priority) as *mut u32 as *mut ntapi::winapi::ctypes::c_void,
145 mem::size_of::<u32>() as u32,
146 )
147 };
148 ret_val
149 .if_non_null_to_error(|| custom_err_with_code("Setting IO priority failed", ret_val))?;
150 Ok(())
151 }
152
153 pub fn get_id(&self) -> ProcessId {
154 let id = unsafe { GetProcessId(self.handle.entity) };
155 ProcessId(id)
156 }
157
158 #[allow(dead_code)]
159 fn from_non_null(handle: HANDLE) -> Self {
160 Self {
161 handle: handle.into(),
162 }
163 }
164
165 fn from_maybe_null(handle: HANDLE) -> Option<Self> {
166 if !handle.is_null() {
167 Some(Self {
168 handle: handle.into(),
169 })
170 } else {
171 None
172 }
173 }
174}
175
176impl TryFrom<ProcessId> for Process {
177 type Error = io::Error;
178
179 fn try_from(value: ProcessId) -> Result<Self, Self::Error> {
180 Process::from_id(value)
181 }
182}
183
184#[derive(Copy, Clone, Eq, PartialEq, Debug)]
186pub struct ProcessId(pub(crate) u32);
187
188impl ProcessId {
189 pub fn current() -> Self {
191 Self(unsafe { GetCurrentProcessId() })
192 }
193}
194
195pub struct Thread {
197 handle: AutoClose<HANDLE>,
198}
199
200impl Thread {
201 pub fn current() -> Self {
205 let pseudo_handle = unsafe { GetCurrentThread() };
206 Self::from_maybe_null(pseudo_handle).expect("Pseudo thread handle should never be null")
207 }
208
209 pub fn from_id<I>(id: I) -> io::Result<Self>
213 where
214 I: Into<ThreadId>,
215 {
216 let raw_handle = unsafe { OpenThread(THREAD_ALL_ACCESS, false, id.into().0)? };
217 Ok(Self {
218 handle: raw_handle.into(),
219 })
220 }
221
222 pub fn begin_background_mode() -> io::Result<()> {
226 unsafe { SetThreadPriority(Self::current().handle.entity, THREAD_MODE_BACKGROUND_BEGIN)? };
227 Ok(())
228 }
229
230 pub fn end_background_mode() -> io::Result<()> {
232 unsafe { SetThreadPriority(Self::current().handle.entity, THREAD_MODE_BACKGROUND_END)? };
233 Ok(())
234 }
235
236 pub fn set_priority(&mut self, priority: ThreadPriority) -> Result<(), io::Error> {
248 unsafe { SetThreadPriority(self.handle.entity, priority.into())? };
249 Ok(())
250 }
251
252 pub fn get_id(&self) -> ThreadId {
253 let id = unsafe { GetThreadId(self.handle.entity) };
254 ThreadId(id)
255 }
256
257 #[allow(dead_code)]
258 fn from_non_null(handle: HANDLE) -> Self {
259 Self {
260 handle: handle.into(),
261 }
262 }
263
264 fn from_maybe_null(handle: HANDLE) -> Option<Self> {
265 if !handle.is_null() {
266 Some(Self {
267 handle: handle.into(),
268 })
269 } else {
270 None
271 }
272 }
273}
274
275impl TryFrom<ThreadId> for Thread {
276 type Error = io::Error;
277
278 fn try_from(value: ThreadId) -> Result<Self, Self::Error> {
279 Thread::from_id(value)
280 }
281}
282
283#[derive(Copy, Clone, Eq, PartialEq, Debug)]
285pub struct ThreadId(pub(crate) u32);
286
287impl ThreadId {
288 pub fn current() -> Self {
290 Self(unsafe { GetCurrentThreadId() })
291 }
292}
293
294#[derive(Copy, Clone, Debug)]
296pub struct ThreadInfo {
297 raw_entry: THREADENTRY32,
298}
299
300impl ThreadInfo {
301 pub fn all_threads() -> io::Result<Vec<Self>> {
303 #[inline(always)]
304 fn get_empty_thread_entry() -> THREADENTRY32 {
305 THREADENTRY32 {
306 dwSize: mem::size_of::<THREADENTRY32>().try_into().unwrap(),
307 ..Default::default()
308 }
309 }
310 let mut result: Vec<Self> = Vec::new();
311 let snapshot: AutoClose<HANDLE> =
312 unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0)? }.into();
313
314 let mut thread_entry = get_empty_thread_entry();
315 unsafe {
316 Thread32First(snapshot.entity, &mut thread_entry)?;
317 }
318 result.push(Self::from_raw(thread_entry));
319 loop {
320 let mut thread_entry = get_empty_thread_entry();
321 let next_ret_val = unsafe { Thread32Next(snapshot.entity, &mut thread_entry) };
322 if next_ret_val.is_ok() {
323 result.push(Self::from_raw(thread_entry));
324 } else {
325 break;
326 }
327 }
328 Ok(result)
329 }
330
331 pub fn all_process_threads<P>(process_id: P) -> io::Result<Vec<Self>>
333 where
334 P: Into<ProcessId>,
335 {
336 let pid: ProcessId = process_id.into();
337 let result = Self::all_threads()?
338 .into_iter()
339 .filter(|thread_info| thread_info.get_owner_process_id() == pid)
340 .collect();
341 Ok(result)
342 }
343
344 fn from_raw(raw_info: THREADENTRY32) -> Self {
345 ThreadInfo {
346 raw_entry: raw_info,
347 }
348 }
349
350 pub fn get_thread_id(&self) -> ThreadId {
352 ThreadId(self.raw_entry.th32ThreadID)
353 }
354
355 pub fn get_owner_process_id(&self) -> ProcessId {
357 ProcessId(self.raw_entry.th32OwnerProcessID)
358 }
359}
360
361#[derive(IntoPrimitive, Clone, Copy, Eq, PartialEq, Debug)]
363#[repr(u32)]
364pub enum ProcessPriority {
365 Idle = Threading::IDLE_PRIORITY_CLASS.0,
366 BelowNormal = Threading::BELOW_NORMAL_PRIORITY_CLASS.0,
367 Normal = Threading::NORMAL_PRIORITY_CLASS.0,
368 AboveNormal = Threading::ABOVE_NORMAL_PRIORITY_CLASS.0,
369 High = Threading::HIGH_PRIORITY_CLASS.0,
370 Realtime = Threading::REALTIME_PRIORITY_CLASS.0,
371}
372
373impl From<ProcessPriority> for PROCESS_CREATION_FLAGS {
374 fn from(value: ProcessPriority) -> Self {
375 PROCESS_CREATION_FLAGS(value.into())
376 }
377}
378
379#[derive(IntoPrimitive, Clone, Copy, Eq, PartialEq, Debug)]
381#[repr(i32)]
382pub enum ThreadPriority {
383 Idle = Threading::THREAD_PRIORITY_IDLE.0,
384 Lowest = Threading::THREAD_PRIORITY_LOWEST.0,
385 BelowNormal = Threading::THREAD_PRIORITY_BELOW_NORMAL.0,
386 Normal = Threading::THREAD_PRIORITY_NORMAL.0,
387 AboveNormal = Threading::THREAD_PRIORITY_ABOVE_NORMAL.0,
388 Highest = Threading::THREAD_PRIORITY_HIGHEST.0,
389 TimeCritical = Threading::THREAD_PRIORITY_TIME_CRITICAL.0,
390}
391
392impl From<ThreadPriority> for THREAD_PRIORITY {
393 fn from(value: ThreadPriority) -> Self {
394 THREAD_PRIORITY(value.into())
395 }
396}
397
398#[derive(IntoPrimitive, TryFromPrimitive, Clone, Copy, Eq, PartialEq, Debug)]
400#[repr(u32)]
401pub enum IoPriority {
402 VeryLow = 0,
403 Low = 1,
404 Normal = 2,
405}
406
407#[derive(IntoPrimitive, Clone, Copy, Debug)]
408#[repr(i32)]
409enum ProcessInformationClass {
410 ProcessIoPriority = ProcessIoPriority as i32,
411}
412
413impl From<ProcessInformationClass> for PROCESSINFOCLASS {
414 fn from(value: ProcessInformationClass) -> Self {
415 PROCESSINFOCLASS(value.into())
416 }
417}
418
419pub struct ModuleHandle {
421 #[allow(dead_code)]
422 raw_handle: HMODULE,
423}
424
425impl ModuleHandle {
426 pub fn get_current() -> io::Result<Self> {
428 let raw_handle = unsafe {
429 let mut h_module: HMODULE = Default::default();
430 GetModuleHandleExW(0, None, &mut h_module)?;
431 h_module.if_null_get_last_error()?
432 };
433 Ok(ModuleHandle { raw_handle })
434 }
435}
436
437#[cfg(test)]
438mod tests {
439 use more_asserts::*;
440
441 #[cfg(feature = "ui")]
442 use crate::ui::WindowHandle;
443
444 use super::*;
445
446 #[test]
447 fn get_all_threads() -> io::Result<()> {
448 let all_threads = ThreadInfo::all_threads()?;
449 assert_gt!(all_threads.len(), 0);
450 Ok(())
451 }
452
453 #[test]
454 fn get_all_process_threads() -> io::Result<()> {
455 let all_threads = ThreadInfo::all_process_threads(Process::current().get_id())?;
456 assert_gt!(all_threads.len(), 0);
457 Ok(())
458 }
459
460 #[cfg(feature = "ui")]
461 #[test]
462 fn get_all_threads_and_windows() -> io::Result<()> {
463 let all_threads = ThreadInfo::all_threads()?;
464 let all_windows: Vec<WindowHandle> = all_threads
465 .into_iter()
466 .flat_map(|thread_info| WindowHandle::get_nonchild_windows(thread_info.get_thread_id()))
467 .collect();
468 assert_gt!(all_windows.len(), 0);
469 Ok(())
470 }
471
472 #[test]
473 fn set_get_io_priority() -> io::Result<()> {
474 let mut curr_process = Process::current();
475 let target_priority = IoPriority::Low;
476 curr_process.set_io_priority(target_priority)?;
477 let priority = curr_process.get_io_priority()?.unwrap();
478 assert_eq!(priority, target_priority);
479 Ok(())
480 }
481}