#![expect(unsafe_code, reason = "read-only Win32 window and cursor queries")]
use windows_sys::Win32::Foundation::{HWND, POINT};
use windows_sys::Win32::UI::WindowsAndMessaging::{
GA_ROOT, GetAncestor, GetClassNameW, GetCursorPos, GetDesktopWindow, GetForegroundWindow,
GetShellWindow, GetWindowThreadProcessId, WindowFromPoint,
};
use super::hook::application_for_process;
use crate::{PointerContext, PointerTarget};
pub(crate) fn pointer_context_supported() -> bool {
true
}
fn process_id(window: HWND) -> Option<i32> {
if window.is_null() {
return None;
}
let mut pid = 0;
if unsafe { GetWindowThreadProcessId(window, &raw mut pid) } == 0 {
return None;
}
i32::try_from(pid).ok().filter(|pid| *pid > 0)
}
fn window_target(window: HWND) -> Option<PointerTarget> {
Some(PointerTarget::Window {
process_id: process_id(window)?,
window_id: window as usize as u64,
})
}
fn is_desktop(window: HWND) -> bool {
let desktop = unsafe { GetDesktopWindow() };
if window == desktop {
return true;
}
let shell = unsafe { GetShellWindow() };
if shell.is_null() {
return false;
}
if window == shell {
return true;
}
let Some(shell_pid) = process_id(shell) else {
return false;
};
if process_id(window) != Some(shell_pid) {
return false;
}
let mut class = [0u16; 128];
let len = unsafe { GetClassNameW(window, class.as_mut_ptr(), 128) };
let Ok(len) = usize::try_from(len) else {
return false;
};
matches!(
String::from_utf16_lossy(&class[..len]).as_str(),
"WorkerW" | "Progman"
)
}
pub(crate) fn pointer_context() -> Option<PointerContext> {
let mut point = POINT::default();
if unsafe { GetCursorPos(&raw mut point) } == 0 {
return None;
}
let child = unsafe { WindowFromPoint(point) };
if child.is_null() {
return None;
}
let window = unsafe { GetAncestor(child, GA_ROOT) };
if window.is_null() {
return None;
}
if is_desktop(window) {
return Some(PointerContext {
app: None,
target: PointerTarget::Desktop,
});
}
let target = window_target(window)?;
let PointerTarget::Window { process_id, .. } = target else {
return None;
};
Some(PointerContext {
app: Some(application_for_process(process_id.cast_unsigned())?),
target,
})
}
pub(crate) fn pointer_target_is_focused(expected: PointerTarget) -> bool {
window_target(unsafe { GetForegroundWindow() }) == Some(expected)
}