From ae1c72a4fa538358167a3f29e6ab01a6591d1348 Mon Sep 17 00:00:00 2001 From: MaulingMonkey Date: Fri, 11 Feb 2022 23:27:14 -0800 Subject: [PATCH] misc. HWND APIs for future hwnd thread ownership testing --- thindx/src/_lib.rs | 3 + thindx/src/error_kind.rs | 1 + thindx/src/errors.rs | 3 + thindx/src/headers/_misc/cpp2url.md | 1 - .../headers/processthreadsapi.h/cpp2url.md | 4 + .../functions/get_current.rs | 37 +++ .../processthreadsapi.h/processthreadsapi.rs | 5 + thindx/src/headers/winuser.h/cpp2url.md | 18 ++ .../src/headers/winuser.h/functions/window.rs | 228 ++++++++++++++++++ thindx/src/headers/winuser.h/winuser.rs | 5 + thindx/src/namespaces/win32.rs | 4 + xtask/Cargo.lock | 2 +- 12 files changed, 309 insertions(+), 2 deletions(-) create mode 100644 thindx/src/headers/processthreadsapi.h/cpp2url.md create mode 100644 thindx/src/headers/processthreadsapi.h/functions/get_current.rs create mode 100644 thindx/src/headers/processthreadsapi.h/processthreadsapi.rs create mode 100644 thindx/src/headers/winuser.h/cpp2url.md create mode 100644 thindx/src/headers/winuser.h/functions/window.rs create mode 100644 thindx/src/headers/winuser.h/winuser.rs create mode 100644 thindx/src/namespaces/win32.rs diff --git a/thindx/src/_lib.rs b/thindx/src/_lib.rs index b1c4ee54..c5860c53 100644 --- a/thindx/src/_lib.rs +++ b/thindx/src/_lib.rs @@ -33,6 +33,7 @@ mods! { pub mod d3d11; pub mod wkpdid; pub mod xinput; + pub mod win32; } #[path="headers/d3d9.h/d3d9.rs"] mod d3d9_h; // d3d9 mod @@ -42,7 +43,9 @@ mods! { #[path="headers/d3dcommon.h/d3dcommon.rs"] mod d3dcommon_h; // d3d mod #[path="headers/d3dcompiler.h/d3dcompiler.rs"] mod d3dcompiler_h; // d3d mod #[path="headers/guiddef.h/guiddef.rs"] mod guiddef_h; + #[path="headers/processthreadsapi.h/processthreadsapi.rs"] mod processthreadsapi_h; #[path="headers/unknwn.h/unknwn.rs"] mod unknwn_h; + #[path="headers/winuser.h/winuser.rs"] mod winuser_h; #[path="headers/xinput.h/xinput.rs"] mod xinput_h; #[path="traits/_traits.rs"] mod traits; diff --git a/thindx/src/error_kind.rs b/thindx/src/error_kind.rs index 5cb1b9d2..70bec0cd 100644 --- a/thindx/src/error_kind.rs +++ b/thindx/src/error_kind.rs @@ -122,6 +122,7 @@ impl ErrorKind { ERROR::FILE_EXISTS => Some(("ERROR_FILE_EXISTS", "The file exists.")), ERROR::BAD_ARGUMENTS => Some(("ERROR_BAD_ARGUMENTS", "One or more arguments are not correct.")), ERROR::DEVICE_NOT_CONNECTED => Some(("ERROR_DEVICE_NOT_CONNECTED", "The device is not connected.")), + ERROR::INVALID_WINDOW_HANDLE => Some(("ERROR_INVALID_WINDOW_HANDLE", "Invalid window handle.")), THINERR::NONSPECIFIC => Some(("THINERR_NONSPECIFIC", "A nonspecific error of some sort occured.")), THINERR::DEVICE_MISMATCH => Some(("THINERR_DEVICE_MISMATCH", "Resource belonging to one Device was passed to a different Device. To avoid undefined behavior, DirectX was not called.")), diff --git a/thindx/src/errors.rs b/thindx/src/errors.rs index 124ae94f..16de190f 100644 --- a/thindx/src/errors.rs +++ b/thindx/src/errors.rs @@ -344,6 +344,9 @@ pub mod ERROR { /// The device is not connected. pub const DEVICE_NOT_CONNECTED : ErrorKind = ErrorKind(ERROR_DEVICE_NOT_CONNECTED as _); + + /// Invalid window handle. + pub const INVALID_WINDOW_HANDLE : ErrorKind = ErrorKind(ERROR_INVALID_WINDOW_HANDLE as _); } /// `0x0000....` • \[[docs.microsoft.com](https://docs.microsoft.com/en-us/windows/win32/learnwin32/error-handling-in-com)\] • Win32/COM success "[ErrorKind]"s
diff --git a/thindx/src/headers/_misc/cpp2url.md b/thindx/src/headers/_misc/cpp2url.md index 7bae88ac..98c56da8 100644 --- a/thindx/src/headers/_misc/cpp2url.md +++ b/thindx/src/headers/_misc/cpp2url.md @@ -2,6 +2,5 @@ [BOOL]: https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types#BOOL [GUID]: https://docs.microsoft.com/en-us/windows/win32/api/guiddef/ns-guiddef-guid [HRESULT]: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/0642cb2f-2075-4469-918c-4441e69c548a -[HWND]: https://docs.microsoft.com/en-us/windows/win32/learnwin32/what-is-a-window- [LUID]: https://docs.microsoft.com/en-us/previous-versions/windows/hardware/drivers/ff549708(v=vs.85) [RECT]: https://docs.microsoft.com/en-us/windows/win32/api/windef/ns-windef-rect diff --git a/thindx/src/headers/processthreadsapi.h/cpp2url.md b/thindx/src/headers/processthreadsapi.h/cpp2url.md new file mode 100644 index 00000000..639811be --- /dev/null +++ b/thindx/src/headers/processthreadsapi.h/cpp2url.md @@ -0,0 +1,4 @@ + + +[GetCurrentProcessId]: https://docs.microsoft.com/en-gb/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentprocessid +[GetCurrentThreadId]: https://docs.microsoft.com/en-gb/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentthreadid diff --git a/thindx/src/headers/processthreadsapi.h/functions/get_current.rs b/thindx/src/headers/processthreadsapi.h/functions/get_current.rs new file mode 100644 index 00000000..c08dbbbb --- /dev/null +++ b/thindx/src/headers/processthreadsapi.h/functions/get_current.rs @@ -0,0 +1,37 @@ +use winapi::um::processthreadsapi::*; + + + +/// \[[docs.microsoft.com](https://docs.microsoft.com/en-gb/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentprocessid)\] +/// GetCurrentProcessId +/// +/// ### Example +/// ```rust +/// # use thindx::*; +/// let process_id = win32::get_current_process_id(); +/// assert!(process_id != 0); +/// ``` +pub fn get_current_process_id() -> u32 { + fn_context!(win32::get_current_process_id => GetCurrentThreadId); + unsafe { GetCurrentProcessId() } +} + +/// \[[docs.microsoft.com](https://docs.microsoft.com/en-gb/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentthreadid)\] +/// GetCurrentThreadId +/// +/// ### Example +/// ```rust +/// # use thindx::*; +/// let thread_id = win32::get_current_thread_id(); +/// assert!(thread_id != 0); +/// ``` +pub fn get_current_thread_id() -> u32 { + // XXX: We could make this return NonZeroU32: + // > Note that no thread identifier will ever be 0. + // > https://docs.microsoft.com/en-us/windows/win32/procthread/thread-handles-and-identifiers + // However, that might be awkward to work with. + // We could have an alternative function...? + // Also, what if we run on a hacked up emulator that violates this invariant? + fn_context!(win32::get_current_thread_id => GetCurrentThreadId); + unsafe { GetCurrentThreadId() } +} diff --git a/thindx/src/headers/processthreadsapi.h/processthreadsapi.rs b/thindx/src/headers/processthreadsapi.h/processthreadsapi.rs new file mode 100644 index 00000000..69646787 --- /dev/null +++ b/thindx/src/headers/processthreadsapi.h/processthreadsapi.rs @@ -0,0 +1,5 @@ +mods! { + inl mod functions { + inl mod get_current; + } +} diff --git a/thindx/src/headers/winuser.h/cpp2url.md b/thindx/src/headers/winuser.h/cpp2url.md new file mode 100644 index 00000000..061ec205 --- /dev/null +++ b/thindx/src/headers/winuser.h/cpp2url.md @@ -0,0 +1,18 @@ + + +[DestroyWindow]: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-destroywindow + +[GetClientRect]: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getclientrect +[GetDesktopWindow]: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdesktopwindow + +[GetWindowThreadProcessId]: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowthreadprocessid + +[IsWindow]: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-iswindow +[IsWindowUnicode]: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-iswindowunicode +[IsWindowVisible]: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-iswindowvisible + + + + +[HWND]: https://docs.microsoft.com/en-us/windows/win32/learnwin32/what-is-a-window- + diff --git a/thindx/src/headers/winuser.h/functions/window.rs b/thindx/src/headers/winuser.h/functions/window.rs new file mode 100644 index 00000000..6d89a0df --- /dev/null +++ b/thindx/src/headers/winuser.h/functions/window.rs @@ -0,0 +1,228 @@ +//! ### Safety +//! +//! `HWND`s are pretty funky. +//! On the one hand, they're supposedly pointers, and in the single threaded days of 16-bit windows, they perhaps were. +//! Modern 32-bit windows has turned these into generational indicies of sorts, and has sunken a lot of time into making Win32 safe/sound despite userspace's abuse. +//! Windows may be owned by another thread, and thus destroyed at any time. +//! Windows may be owned by another process, and thus destroyed at any time. +//! Windows may be owned by another user, probably? +//! HWNDs should generally be treated as weak references without proper validity checking. + +use crate::{Error, ErrorKind, ERROR}; +use crate::d3d::Rect; // ...? + +use winapi::um::winuser::*; + + + +/// \[[docs.microsoft.com](https://docs.microsoft.com/en-us/windows/win32/learnwin32/what-is-a-window-)\] +/// HWND +/// +/// A window handle. +/// Note that Window's definition of a "window" is [pretty expansive](https://docs.microsoft.com/en-us/windows/win32/learnwin32/what-is-a-window-). +/// There's a reason it's named "Windows"! +/// +/// Windows are "windows". +/// Buttons are "windows". +/// The desktop is a "window". +/// ~~You're a "window".~~ +/// Windows belonging to other threads are windows. +/// Windows belonging to other processes are windows. +/// Windows belonging to other users... *maybe* you're isolated from those? But probably not. +pub type HWND = winapi::shared::windef::HWND; + +/// \[[docs.microsoft.com](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-destroywindow)\] +/// DestroyWindow +/// +/// Destroys the specified window. +/// +/// ### ⚠️ Safety ⚠️ +/// * Destroying a window out from underneath a 3D rendering API such as Direct3D is generally unsound. +/// * Destroying a "destroyed" window is unlikely to be sound. While windows itself can handle it, +/// it can result in multiple WM_DESTROY and WM_NCDESTROY events, which the underlying wndprocs likely can't handle. +/// * Destroying a window belonging to another thread or process is an incredibly bad idea, if it even works. +/// * Honestly, you should probably only destroy windows you created, in your own process, on your own thread, and even then be careful! +/// +/// ### Errors +/// * [ERROR::INVALID_WINDOW_HANDLE] +/// +/// ### Example +/// ```rust +/// # use thindx::*; +/// # use std::ptr::*; +/// assert_eq!(ERROR::INVALID_WINDOW_HANDLE, unsafe { win32::destroy_window(null_mut()) }); +/// ``` +pub unsafe fn destroy_window(hwnd: impl Into) -> Result<(), Error> { + fn_context!(win32::destroy_window => DestroyWindow); + let hwnd = hwnd.into(); + let succeeded = unsafe { DestroyWindow(hwnd) != 0 }; + if succeeded { Ok(()) } else { fn_err!(get_last_error()) } +} + +/// \[[docs.microsoft.com](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getclientrect)\] +/// GetClientRect +/// +/// Retrieves the coordinates of a window's client area. +/// The client coordinates specify the upper-left and lower-right corners of the client area. +/// Because client coordinates are relative to the upper-left corner of a window's client area, the coordinates of the upper-left corner are (0,0). +/// +/// ### Errors +/// * [ERROR::INVALID_WINDOW_HANDLE] +/// +/// ### Example +/// ```rust +/// # use thindx::*; +/// # use std::ptr::*; +/// assert_eq!(ERROR::INVALID_WINDOW_HANDLE, win32::get_client_rect(null_mut())); +/// let rect = win32::get_client_rect(win32::get_desktop_window()).unwrap(); +/// assert_eq!(0, rect.left()); +/// assert_eq!(0, rect.top()); +/// assert!(0 != rect.width()); +/// assert!(0 != rect.height()); +/// # for p in 0 .. 8 * std::mem::size_of::() { +/// # let e = win32::get_client_rect((1usize << p) as win32::HWND); // shouldn't crash +/// # if e.is_err() { assert_eq!(ERROR::INVALID_WINDOW_HANDLE, e) } +/// # } +/// ``` +pub fn get_client_rect(hwnd: impl TryInto) -> Result { + fn_context!(win32::get_client_rect => GetClientRect); + let hwnd = hwnd.try_into().map_err(|_| fn_param_error!(hwnd, ERROR::INVALID_WINDOW_HANDLE))?; + let mut rect = Rect::zeroed(); + let succeeded = unsafe { GetClientRect(hwnd, rect.as_mut()) != 0 }; + if succeeded { Ok(rect) } else { fn_err!(get_last_error()) } +} + +/// \[[docs.microsoft.com](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdesktopwindow)\] +/// GetDesktopWindow +/// +/// Retrieves a handle to the desktop window. +/// The desktop window covers the entire screen. +/// The desktop window is the area on top of which other windows are painted. +/// +/// ### Example +/// ```rust +/// # use thindx::win32; +/// let hwnd = win32::get_desktop_window(); +/// # assert!(win32::is_window(hwnd)); +/// ``` +#[must_use] pub fn get_desktop_window() -> HWND { + fn_context!(win32::get_desktop_window => GetDesktopWindow); + unsafe { GetDesktopWindow() } +} + +/// \[[docs.microsoft.com](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowthreadprocessid)\] +/// GetWindowThreadProcessId +/// +/// Retrieves the identifier of the thread that created the specified window and, optionally, the identifier of the process that created the window. +/// +/// ### Returns +/// * Ok((thread_id, process_id)) +/// * [ERROR::INVALID_WINDOW_HANDLE] +/// +/// ### Example +/// ```rust +/// # use thindx::*; +/// # let hwnd = win32::get_desktop_window(); +/// let (thread, process) = win32::get_window_thread_process_id(hwnd).unwrap(); +/// let hwnd_belongs_to_this_thread = thread == win32::get_current_thread_id(); +/// # assert!(!hwnd_belongs_to_this_thread, "desktop doesn't belong to us!"); +/// # +/// # for p in 0 .. 8 * std::mem::size_of::() { +/// # let e = win32::get_window_thread_process_id((1usize << p) as win32::HWND); // shouldn't crash +/// # if e.is_err() { assert_eq!(ERROR::INVALID_WINDOW_HANDLE, e) } +/// # } +/// ``` +#[must_use] pub fn get_window_thread_process_id(hwnd: impl Into) -> Result<(u32, u32), Error> { + fn_context!(win32::get_window_thread_process_id => GetWindowThreadProcessId); + let hwnd = hwnd.into(); + let mut pid = 0; + let tid = unsafe { GetWindowThreadProcessId(hwnd, &mut pid) }; + if tid != 0 { Ok((tid, pid)) } else { fn_err!(get_last_error()) } +} + +/// \[[docs.microsoft.com](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-iswindow)\] +/// IsWindow +/// +/// Determines whether the specified window handle identifies an existing window. +/// +/// Valid uses of this function are few and far between - windows belonging to another thread +/// or process could be destroyed immediately after this returns true, and invalidated handles +/// might suddenly spring back to life as the handle value is reused. +/// +/// ### Example +/// ```rust +/// # use thindx::win32; +/// # let valid_hwnd = win32::get_desktop_window(); +/// # let invalid_hwnd : win32::HWND = !42 as _; +/// assert!( win32::is_window( valid_hwnd)); +/// assert!(!win32::is_window(invalid_hwnd)); +/// assert!(!win32::is_window(std::ptr::null_mut())); +/// # for p in 0 .. 8 * std::mem::size_of::() { +/// # let _ = win32::is_window((1usize << p) as win32::HWND); // shouldn't crash +/// # } +/// ``` +#[must_use] pub fn is_window(hwnd: impl TryInto) -> bool { + fn_context!(win32::is_window => IsWindow); + match hwnd.try_into() { + Ok(hwnd) => unsafe { IsWindow(hwnd) != 0 }, + Err(_) => false, + } +} + +/// \[[docs.microsoft.com](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-iswindow)\] +/// IsWindowUnicode +/// +/// Determines whether the specified window is a native Unicode window. +/// +/// ### Returns +/// * `true` if the window's class was registered with `RegisterClassW` +/// * `false` if the window's class was registered with `RegisterClassA` +/// * `false` if the window isn't valid, probably? (TryInto failed, HWND null/dangling/destroyed, ...) +/// +/// ### Example +/// ```rust +/// # use thindx::win32; +/// # let unicode_hwnd = win32::get_desktop_window(); // TODO: replace with an explicitly created unicode hwnd +/// assert!( win32::is_window_unicode(unicode_hwnd)); +/// assert!(!win32::is_window_unicode(std::ptr::null_mut())); +/// # for p in 0 .. 8 * std::mem::size_of::() { +/// # let _ = win32::is_window_unicode((1usize << p) as win32::HWND); // shouldn't crash +/// # } +/// ``` +#[must_use] pub fn is_window_unicode(hwnd: impl TryInto) -> bool { + fn_context!(win32::is_window_unicode => IsWindowUnicode); + match hwnd.try_into() { + Ok(hwnd) => unsafe { IsWindowUnicode(hwnd) != 0 }, + Err(_) => false, + } +} + +/// \[[docs.microsoft.com](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-iswindow)\] +/// IsWindowVisible +/// +/// Determines whether the specified window is WS_VISIBLE. +/// May return `true` even if the window is totally obscured by other windows, clipped out-of-bounds, etc. +/// +/// ### Example +/// ```rust +/// # use thindx::win32; +/// # let hwnd = win32::get_desktop_window(); // TODO: replace with an explicitly created unicode hwnd +/// assert!( win32::is_window_visible(hwnd)); +/// assert!(!win32::is_window_visible(std::ptr::null_mut())); +/// # for p in 0 .. 8 * std::mem::size_of::() { +/// # let _ = win32::is_window_visible((1usize << p) as win32::HWND); // shouldn't crash +/// # } +/// ``` +#[must_use] pub fn is_window_visible(hwnd: impl TryInto) -> bool { + fn_context!(win32::is_window_unicode => IsWindowVisible); + match hwnd.try_into() { + Ok(hwnd) => unsafe { IsWindowVisible(hwnd) != 0 }, + Err(_) => false, + } +} + + + +#[must_use] fn get_last_error() -> ErrorKind { + ErrorKind(unsafe { winapi::um::errhandlingapi::GetLastError() } as _) +} diff --git a/thindx/src/headers/winuser.h/winuser.rs b/thindx/src/headers/winuser.h/winuser.rs new file mode 100644 index 00000000..2f50ff22 --- /dev/null +++ b/thindx/src/headers/winuser.h/winuser.rs @@ -0,0 +1,5 @@ +mods! { + inl mod functions { + inl mod window; + } +} diff --git a/thindx/src/namespaces/win32.rs b/thindx/src/namespaces/win32.rs new file mode 100644 index 00000000..d857b48d --- /dev/null +++ b/thindx/src/namespaces/win32.rs @@ -0,0 +1,4 @@ +//! General windows APIs + +pub use crate::processthreadsapi_h::*; +pub use crate::winuser_h::*; diff --git a/xtask/Cargo.lock b/xtask/Cargo.lock index 99fd1b41..26fe9232 100644 --- a/xtask/Cargo.lock +++ b/xtask/Cargo.lock @@ -44,7 +44,7 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "maulingmonkey-windows-sdk-scanner" version = "0.0.0-git" -source = "git+https://github.com/MaulingMonkey/windows-sdk-scanner#d81e08e9df6a7fc7de2fea3a2a020717f1bddd68" +source = "git+https://github.com/MaulingMonkey/windows-sdk-scanner#b58a05e9515f664b1a014c8cc158846c82b03484" dependencies = [ "abibool", "abistr",