Skip to content

Commit

Permalink
misc. HWND APIs for future hwnd thread ownership testing
Browse files Browse the repository at this point in the history
  • Loading branch information
MaulingMonkey committed Feb 12, 2022
1 parent 4627cc5 commit ae1c72a
Show file tree
Hide file tree
Showing 12 changed files with 309 additions and 2 deletions.
3 changes: 3 additions & 0 deletions thindx/src/_lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand Down
1 change: 1 addition & 0 deletions thindx/src/error_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.")),
Expand Down
3 changes: 3 additions & 0 deletions thindx/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<br>
Expand Down
1 change: 0 additions & 1 deletion thindx/src/headers/_misc/cpp2url.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 4 additions & 0 deletions thindx/src/headers/processthreadsapi.h/cpp2url.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<!-- functions -->

[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
37 changes: 37 additions & 0 deletions thindx/src/headers/processthreadsapi.h/functions/get_current.rs
Original file line number Diff line number Diff line change
@@ -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() }
}
5 changes: 5 additions & 0 deletions thindx/src/headers/processthreadsapi.h/processthreadsapi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mods! {
inl mod functions {
inl mod get_current;
}
}
18 changes: 18 additions & 0 deletions thindx/src/headers/winuser.h/cpp2url.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!-- functions -->

[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



<!-- types -->
[HWND]: https://docs.microsoft.com/en-us/windows/win32/learnwin32/what-is-a-window-

228 changes: 228 additions & 0 deletions thindx/src/headers/winuser.h/functions/window.rs
Original file line number Diff line number Diff line change
@@ -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<HWND>) -> 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::<win32::HWND>() {
/// # 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<HWND>) -> Result<Rect, Error> {
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::<win32::HWND>() {
/// # 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<HWND>) -> 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::<win32::HWND>() {
/// # let _ = win32::is_window((1usize << p) as win32::HWND); // shouldn't crash
/// # }
/// ```
#[must_use] pub fn is_window(hwnd: impl TryInto<HWND>) -> 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::<win32::HWND>() {
/// # let _ = win32::is_window_unicode((1usize << p) as win32::HWND); // shouldn't crash
/// # }
/// ```
#[must_use] pub fn is_window_unicode(hwnd: impl TryInto<HWND>) -> 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::<win32::HWND>() {
/// # let _ = win32::is_window_visible((1usize << p) as win32::HWND); // shouldn't crash
/// # }
/// ```
#[must_use] pub fn is_window_visible(hwnd: impl TryInto<HWND>) -> 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 _)
}
5 changes: 5 additions & 0 deletions thindx/src/headers/winuser.h/winuser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mods! {
inl mod functions {
inl mod window;
}
}
4 changes: 4 additions & 0 deletions thindx/src/namespaces/win32.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
//! General windows APIs

pub use crate::processthreadsapi_h::*;
pub use crate::winuser_h::*;
2 changes: 1 addition & 1 deletion xtask/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit ae1c72a

Please # to comment.