use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle, RawHandle};
use windows::Win32::System::Threading::{
GetCurrentProcess, GetCurrentThread, GetCurrentThreadId, GetProcessTimes, GetThreadTimes,
THREAD_QUERY_LIMITED_INFORMATION,
};
use windows::Win32::{Foundation::*, UI::WindowsAndMessaging::*};
use windows::core::*;
#[inline(always)]
pub(crate) fn get_window_long_ptr(hwnd: HWND, index: WINDOW_LONG_PTR_INDEX) -> Result<isize> {
unsafe {
SetLastError(ERROR_SUCCESS);
let res = GetWindowLongPtrW(hwnd, index);
let err = Error::from_thread();
if err.code() != S_OK {
return Err(err);
}
Ok(res)
}
}
#[inline(always)]
pub(crate) fn set_window_long_ptr(
hwnd: HWND,
index: WINDOW_LONG_PTR_INDEX,
value: isize,
) -> Result<isize> {
unsafe {
SetLastError(ERROR_SUCCESS);
let res = SetWindowLongPtrW(hwnd, index, value);
if res == 0 {
let err = Error::from_thread();
if err.code() != S_OK {
return Err(err);
}
}
Ok(res)
}
}
#[inline(always)]
fn get_window_relative(hwnd: HWND, ucmd: GET_WINDOW_CMD) -> Result<Option<HWND>> {
unsafe {
SetLastError(ERROR_SUCCESS);
match GetWindow(hwnd, ucmd) {
Ok(found) => Ok(Some(found)),
Err(err) if err.code() == S_OK => Ok(None),
Err(err) => Err(err),
}
}
}
#[inline(always)]
pub(crate) fn get_window_above(hwnd: HWND) -> Result<Option<HWND>> {
get_window_relative(hwnd, GW_HWNDPREV)
}
#[inline(always)]
pub(crate) fn get_window_below(hwnd: HWND) -> Result<Option<HWND>> {
get_window_relative(hwnd, GW_HWNDNEXT)
}
#[inline(always)]
pub(crate) fn get_foreground_window() -> Option<HWND> {
let hwnd = unsafe { GetForegroundWindow() };
if hwnd.is_invalid() { None } else { Some(hwnd) }
}
#[inline(always)]
pub(crate) fn is_window_visible(hwnd: HWND) -> bool {
unsafe { IsWindowVisible(hwnd) }.as_bool()
}
#[inline(always)]
pub(crate) fn is_window_always_on_top(hwnd: HWND) -> Result<bool> {
let ex_style = get_window_long_ptr(hwnd, GWL_EXSTYLE)?;
Ok((ex_style as u32) & WS_EX_TOPMOST.0 != 0)
}
#[inline(always)]
pub(crate) fn set_window_owner(hwnd: HWND, owner: HWND) -> Result<()> {
set_window_long_ptr(hwnd, GWLP_HWNDPARENT, owner.0 as isize).map(|_| ())
}
#[inline(always)]
pub(crate) fn clear_window_owner(hwnd: HWND) -> Result<()> {
set_window_long_ptr(hwnd, GWLP_HWNDPARENT, 0).map(|_| ())
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct CpuTimes {
pub kernel_100ns: u64,
pub user_100ns: u64,
}
impl CpuTimes {
pub const fn total_100ns(&self) -> u64 {
self.kernel_100ns + self.user_100ns
}
pub const fn kernel_us(&self) -> u64 {
self.kernel_100ns / 10
}
pub const fn user_us(&self) -> u64 {
self.user_100ns / 10
}
pub const fn total_us(&self) -> u64 {
self.total_100ns() / 10
}
}
#[inline(always)]
const fn filetime_to_100ns(ft: FILETIME) -> u64 {
((ft.dwHighDateTime as u64) << 32) | ft.dwLowDateTime as u64
}
#[inline(always)]
pub fn get_current_thread_id() -> u32 {
unsafe { GetCurrentThreadId() }
}
pub fn duplicate_current_thread_handle() -> Result<OwnedHandle> {
unsafe {
let process = GetCurrentProcess();
let mut duplicated = HANDLE::default();
DuplicateHandle(
process,
GetCurrentThread(),
process,
&mut duplicated,
THREAD_QUERY_LIMITED_INFORMATION.0,
false,
DUPLICATE_HANDLE_OPTIONS(0),
)?;
Ok(OwnedHandle::from_raw_handle(duplicated.0 as RawHandle))
}
}
pub fn get_thread_times(thread: &OwnedHandle) -> Result<CpuTimes> {
let handle = HANDLE(thread.as_raw_handle() as _);
let mut creation = FILETIME::default();
let mut exit = FILETIME::default();
let mut kernel = FILETIME::default();
let mut user = FILETIME::default();
unsafe {
GetThreadTimes(handle, &mut creation, &mut exit, &mut kernel, &mut user)?;
}
Ok(CpuTimes {
kernel_100ns: filetime_to_100ns(kernel),
user_100ns: filetime_to_100ns(user),
})
}
pub fn get_process_times() -> Result<CpuTimes> {
let mut creation = FILETIME::default();
let mut exit = FILETIME::default();
let mut kernel = FILETIME::default();
let mut user = FILETIME::default();
unsafe {
GetProcessTimes(
GetCurrentProcess(),
&mut creation,
&mut exit,
&mut kernel,
&mut user,
)?;
}
Ok(CpuTimes {
kernel_100ns: filetime_to_100ns(kernel),
user_100ns: filetime_to_100ns(user),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn get_window_long_ptr_with_null_hwnd_returns_invalid_window_handle() {
let err = get_window_long_ptr(HWND::default(), GWL_STYLE).unwrap_err();
assert_eq!(err.code(), ERROR_INVALID_WINDOW_HANDLE.to_hresult());
}
#[test]
fn set_window_long_ptr_with_null_hwnd_returns_invalid_window_handle() {
let err = set_window_long_ptr(HWND::default(), GWL_EXSTYLE, 0).unwrap_err();
assert_eq!(err.code(), ERROR_INVALID_WINDOW_HANDLE.to_hresult());
}
#[test]
fn get_window_long_ptr_succeeds_on_desktop_window() {
let hwnd = unsafe { GetDesktopWindow() };
let style =
get_window_long_ptr(hwnd, GWL_STYLE).expect("desktop window style must be readable");
assert_ne!(style, 0);
}
#[test]
fn get_window_long_ptr_clears_stale_last_error_before_call() {
let hwnd = unsafe { GetDesktopWindow() };
unsafe { SetLastError(ERROR_ACCESS_DENIED) };
let res = get_window_long_ptr(hwnd, GWL_STYLE);
assert!(res.is_ok());
}
fn create_test_hwnd(title: PCWSTR) -> HWND {
create_test_window(title, WINDOW_STYLE(WS_POPUP.0), None)
}
fn create_test_child_hwnd(parent: HWND, title: PCWSTR) -> HWND {
create_test_window(title, WINDOW_STYLE(WS_CHILD.0), Some(parent))
}
fn create_test_window(title: PCWSTR, style: WINDOW_STYLE, parent: Option<HWND>) -> HWND {
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
unsafe {
let hinstance = GetModuleHandleW(None).expect("GetModuleHandleW");
CreateWindowExW(
WINDOW_EX_STYLE(0),
w!("Static"),
title,
style,
0,
0,
0,
0,
parent,
None,
Some(hinstance.into()),
None,
)
.expect("CreateWindowExW should create a hidden test window")
}
}
fn destroy_test_hwnd(hwnd: HWND) {
unsafe {
let _ = DestroyWindow(hwnd);
}
}
fn insert_after(hwnd: HWND, after: HWND) {
unsafe {
SetWindowPos(
hwnd,
Some(after),
0,
0,
0,
0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE,
)
.expect("SetWindowPos should reorder self-owned test windows");
}
}
#[test]
fn get_window_above_and_below_report_real_two_window_zorder() {
let parent = create_test_hwnd(w!("wintf-zorder-test-parent"));
let first = create_test_child_hwnd(parent, w!("wintf-zorder-test-first"));
let second = create_test_child_hwnd(parent, w!("wintf-zorder-test-second"));
insert_after(second, first);
assert_eq!(
get_window_above(second).expect("GetWindow(GW_HWNDPREV) must succeed"),
Some(first),
"配置 A: second の 1 つ手前は first のはず"
);
assert_eq!(
get_window_below(first).expect("GetWindow(GW_HWNDNEXT) must succeed"),
Some(second),
"配置 A: first の 1 つ背後は second のはず"
);
assert_eq!(
get_window_above(first).expect("GetWindow(GW_HWNDPREV) must succeed"),
None,
"配置 A: first より手前には誰も居ないはず"
);
assert_eq!(
get_window_below(second).expect("GetWindow(GW_HWNDNEXT) must succeed"),
None,
"配置 A: second より背後には誰も居ないはず"
);
insert_after(first, second);
assert_eq!(
get_window_above(first).expect("GetWindow(GW_HWNDPREV) must succeed"),
Some(second),
"配置 B: first の 1 つ手前は second のはず"
);
assert_eq!(
get_window_below(second).expect("GetWindow(GW_HWNDNEXT) must succeed"),
Some(first),
"配置 B: second の 1 つ背後は first のはず"
);
assert_eq!(
get_window_above(second).expect("GetWindow(GW_HWNDPREV) must succeed"),
None,
"配置 B: second より手前には誰も居ないはず"
);
assert_eq!(
get_window_below(first).expect("GetWindow(GW_HWNDNEXT) must succeed"),
None,
"配置 B: first より背後には誰も居ないはず"
);
destroy_test_hwnd(second);
destroy_test_hwnd(first);
destroy_test_hwnd(parent);
}
#[test]
fn get_window_above_and_below_report_absence_as_ok_none() {
let desktop = unsafe { GetDesktopWindow() };
assert_eq!(get_window_above(desktop).unwrap(), None);
assert_eq!(get_window_below(desktop).unwrap(), None);
}
#[test]
fn get_window_above_with_null_hwnd_returns_invalid_window_handle() {
let err = get_window_above(HWND::default()).unwrap_err();
assert_eq!(err.code(), ERROR_INVALID_WINDOW_HANDLE.to_hresult());
}
#[test]
fn get_window_below_with_null_hwnd_returns_invalid_window_handle() {
let err = get_window_below(HWND::default()).unwrap_err();
assert_eq!(err.code(), ERROR_INVALID_WINDOW_HANDLE.to_hresult());
}
#[test]
fn set_window_owner_with_null_hwnd_returns_invalid_window_handle() {
let err = set_window_owner(HWND::default(), unsafe { GetDesktopWindow() }).unwrap_err();
assert_eq!(err.code(), ERROR_INVALID_WINDOW_HANDLE.to_hresult());
}
#[test]
fn clear_window_owner_with_null_hwnd_returns_invalid_window_handle() {
let err = clear_window_owner(HWND::default()).unwrap_err();
assert_eq!(err.code(), ERROR_INVALID_WINDOW_HANDLE.to_hresult());
}
#[test]
fn get_foreground_window_returns_absence_or_a_real_window() {
match get_foreground_window() {
None => {}
Some(hwnd) => {
assert!(
!hwnd.is_invalid(),
"不在は None であって NULL ハンドルではない"
);
assert!(
unsafe { IsWindow(Some(hwnd)) }.as_bool(),
"返るのは実在する窓のはず"
);
}
}
}
#[test]
fn is_window_visible_reads_the_style_and_the_whole_ancestor_chain() {
let parent = create_test_hwnd(w!("wintf-visible-test-parent"));
let shown = create_test_window(
w!("wintf-visible-test-shown"),
WINDOW_STYLE(WS_CHILD.0 | WS_VISIBLE.0),
Some(parent),
);
let hidden = create_test_child_hwnd(parent, w!("wintf-visible-test-hidden"));
assert!(!is_window_visible(parent), "親窓はまだ表示していない");
assert!(
!is_window_visible(shown),
"祖先が隠れている子は WS_VISIBLE を持っていても可視ではない"
);
unsafe {
let _ = ShowWindow(parent, SW_SHOWNA);
}
assert!(is_window_visible(parent), "表示した親窓は可視");
assert!(
is_window_visible(shown),
"親が可視になれば WS_VISIBLE の子も可視になる"
);
assert!(
!is_window_visible(hidden),
"WS_VISIBLE を持たない子は親が可視でも不可視"
);
assert!(!is_window_visible(HWND::default()));
destroy_test_hwnd(hidden);
destroy_test_hwnd(shown);
destroy_test_hwnd(parent);
}
#[test]
fn set_then_clear_window_owner_round_trips_on_real_windows() {
let owner = create_test_hwnd(w!("wintf-owner-test-owner"));
let owned = create_test_hwnd(w!("wintf-owner-test-owned"));
assert_eq!(
get_window_long_ptr(owned, GWLP_HWNDPARENT).unwrap(),
0,
"生成直後の owner は未設定のはず"
);
set_window_owner(owned, owner).expect("set_window_owner must succeed");
assert_eq!(
get_window_long_ptr(owned, GWLP_HWNDPARENT).unwrap(),
owner.0 as isize,
"GWLP_HWNDPARENT に owner の HWND が入るはず"
);
assert_eq!(
unsafe { GetWindow(owned, GW_OWNER) }.ok(),
Some(owner),
"GetWindow(GW_OWNER) からも owner が読めるはず"
);
clear_window_owner(owned).expect("clear_window_owner must succeed");
assert_eq!(
get_window_long_ptr(owned, GWLP_HWNDPARENT).unwrap(),
0,
"切離し後の GWLP_HWNDPARENT は 0 のはず"
);
assert_eq!(
unsafe { GetWindow(owned, GW_OWNER) }.ok(),
None,
"切離し後は GetWindow(GW_OWNER) も不在を返すはず"
);
destroy_test_hwnd(owned);
destroy_test_hwnd(owner);
}
}
#[cfg(test)]
#[path = "api_owner_chain_probe_tests.rs"]
mod api_owner_chain_probe_tests;
#[cfg(test)]
#[path = "api_owner_chain_nudge_probe_tests.rs"]
mod api_owner_chain_nudge_probe_tests;