use std::ptr;
use windows::Graphics::Capture::GraphicsCaptureItem;
use windows::Win32::Foundation::{GetLastError, HWND, LPARAM, RECT, TRUE};
use windows::Win32::Graphics::Dwm::{DWMWA_EXTENDED_FRAME_BOUNDS, DwmGetWindowAttribute};
use windows::Win32::Graphics::Gdi::{MONITOR_DEFAULTTONULL, MonitorFromWindow};
use windows::Win32::System::ProcessStatus::GetModuleBaseNameW;
use windows::Win32::System::Threading::{GetCurrentProcessId, OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ};
use windows::Win32::System::WinRT::Graphics::Capture::IGraphicsCaptureItemInterop;
use windows::Win32::UI::HiDpi::GetDpiForWindow;
use windows::Win32::UI::WindowsAndMessaging::{
EnumChildWindows, FindWindowW, GWL_EXSTYLE, GWL_STYLE, GetClientRect, GetDesktopWindow, GetForegroundWindow,
GetWindowLongPtrW, GetWindowRect, GetWindowTextLengthW, GetWindowTextW, GetWindowThreadProcessId, IsWindowVisible,
WS_CHILD, WS_EX_TOOLWINDOW,
};
use windows::core::{BOOL, HSTRING, Owned};
use crate::monitor::Monitor;
use crate::settings::GraphicsCaptureItemType;
#[derive(thiserror::Error, Eq, PartialEq, Clone, Debug)]
pub enum Error {
#[error("No active window found.")]
NoActiveWindow,
#[error("Failed to find a window with the name: {0}")]
NotFound(String),
#[error("Failed to convert a Windows string from UTF-16")]
FailedToConvertWindowsString,
#[error("A Windows API call failed: {0}")]
WindowsError(#[from] windows::core::Error),
}
#[derive(Eq, PartialEq, Clone, Copy, Debug)]
pub struct Window {
window: HWND,
}
unsafe impl Send for Window {}
impl Window {
#[inline]
pub fn foreground() -> Result<Self, Error> {
let window = unsafe { GetForegroundWindow() };
if window.is_invalid() {
return Err(Error::NoActiveWindow);
}
Ok(Self { window })
}
#[inline]
pub fn from_name(title: &str) -> Result<Self, Error> {
let hstring_title = HSTRING::from(title);
let window = unsafe { FindWindowW(None, &hstring_title)? };
if window.is_invalid() {
return Err(Error::NotFound(String::from(title)));
}
Ok(Self { window })
}
#[inline]
pub fn from_contains_name(title: &str) -> Result<Self, Error> {
let windows = Self::enumerate()?;
let mut target_window = None;
for window in windows {
if window.title()?.contains(title) {
target_window = Some(window);
break;
}
}
target_window.map_or_else(|| Err(Error::NotFound(String::from(title))), Ok)
}
#[inline]
pub fn title(&self) -> Result<String, Error> {
let len = unsafe { GetWindowTextLengthW(self.window) };
if len == 0 {
return Ok(String::new());
}
let mut buf = vec![0u16; usize::try_from(len).unwrap() + 1];
let copied = unsafe { GetWindowTextW(self.window, &mut buf) };
if copied == 0 {
return Ok(String::new());
}
let name = String::from_utf16(&buf[..copied as usize]).map_err(|_| Error::FailedToConvertWindowsString)?;
Ok(name)
}
#[inline]
pub fn process_id(&self) -> Result<u32, Error> {
let mut id = 0;
unsafe { GetWindowThreadProcessId(self.window, Some(&mut id)) };
if id == 0 {
return Err(Error::WindowsError(unsafe { GetLastError().into() }));
}
Ok(id)
}
#[inline]
pub fn process_name(&self) -> Result<String, Error> {
let id = self.process_id()?;
let process = unsafe { OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, id) }?;
let process = unsafe { Owned::new(process) };
let mut name = vec![0u16; 260];
let size = unsafe { GetModuleBaseNameW(*process, None, &mut name) };
if size == 0 {
return Err(Error::WindowsError(unsafe { GetLastError().into() }));
}
let name =
String::from_utf16(&name.as_slice().iter().take_while(|ch| **ch != 0x0000).copied().collect::<Vec<u16>>())
.map_err(|_| Error::FailedToConvertWindowsString)?;
Ok(name)
}
#[inline]
#[must_use]
pub fn monitor(&self) -> Option<Monitor> {
let window = self.window;
let monitor = unsafe { MonitorFromWindow(window, MONITOR_DEFAULTTONULL) };
if monitor.is_invalid() { None } else { Some(Monitor::from_raw_hmonitor(monitor.0)) }
}
#[inline]
pub fn rect(&self) -> Result<RECT, Error> {
let mut rect = RECT::default();
let result = unsafe { GetWindowRect(self.window, &mut rect) };
if result.is_ok() { Ok(rect) } else { Err(Error::WindowsError(unsafe { GetLastError().into() })) }
}
#[inline]
pub fn title_bar_height(&self) -> Result<u32, Error> {
let mut window_rect = RECT::default();
let mut client_rect = RECT::default();
unsafe {
DwmGetWindowAttribute(
self.window,
DWMWA_EXTENDED_FRAME_BOUNDS,
&mut window_rect as *mut RECT as *mut std::ffi::c_void,
std::mem::size_of::<RECT>() as u32,
)
}?;
unsafe { GetClientRect(self.window, &mut client_rect) }?;
let window_height = window_rect.bottom - window_rect.top;
let dpi = unsafe { GetDpiForWindow(self.window) };
let client_height = (client_rect.bottom - client_rect.top) * dpi as i32 / 96;
let actual_title_height = (window_height - client_height) as i32;
Ok(actual_title_height as u32)
}
#[inline]
#[must_use]
pub fn is_valid(&self) -> bool {
if !unsafe { IsWindowVisible(self.window).as_bool() } {
return false;
}
let mut id = 0;
unsafe { GetWindowThreadProcessId(self.window, Some(&mut id)) };
if id == unsafe { GetCurrentProcessId() } {
return false;
}
let mut rect = RECT::default();
let result = unsafe { GetClientRect(self.window, &mut rect) };
if result.is_ok() {
#[cfg(target_pointer_width = "64")]
let styles = unsafe { GetWindowLongPtrW(self.window, GWL_STYLE) };
#[cfg(target_pointer_width = "64")]
let ex_styles = unsafe { GetWindowLongPtrW(self.window, GWL_EXSTYLE) };
#[cfg(target_pointer_width = "32")]
let styles = unsafe { GetWindowLongPtrW(self.window, GWL_STYLE) as isize };
#[cfg(target_pointer_width = "32")]
let ex_styles = unsafe { GetWindowLongPtrW(self.window, GWL_EXSTYLE) as isize };
if (ex_styles & isize::try_from(WS_EX_TOOLWINDOW.0).unwrap()) != 0 {
return false;
}
if (styles & isize::try_from(WS_CHILD.0).unwrap()) != 0 {
return false;
}
} else {
return false;
}
true
}
#[inline]
pub fn enumerate() -> Result<Vec<Self>, Error> {
let mut windows: Vec<Self> = Vec::new();
unsafe {
EnumChildWindows(
Some(GetDesktopWindow()),
Some(Self::enum_windows_callback),
LPARAM(ptr::addr_of_mut!(windows) as isize),
)
.ok()?;
};
Ok(windows)
}
pub fn width(&self) -> Result<i32, Error> {
let rect = self.rect()?;
Ok(rect.right - rect.left)
}
pub fn height(&self) -> Result<i32, Error> {
let rect = self.rect()?;
Ok(rect.bottom - rect.top)
}
#[inline]
#[must_use]
pub const fn from_raw_hwnd(hwnd: *mut std::ffi::c_void) -> Self {
Self { window: HWND(hwnd) }
}
#[inline]
#[must_use]
pub const fn as_raw_hwnd(&self) -> *mut std::ffi::c_void {
self.window.0
}
#[inline]
unsafe extern "system" fn enum_windows_callback(window: HWND, vec: LPARAM) -> BOOL {
let windows = unsafe { &mut *(vec.0 as *mut Vec<Self>) };
if Self::from_raw_hwnd(window.0).is_valid() {
windows.push(Self { window });
}
TRUE
}
}
impl TryInto<GraphicsCaptureItemType> for Window {
type Error = windows::core::Error;
#[inline]
fn try_into(self) -> Result<GraphicsCaptureItemType, Self::Error> {
let window = HWND(self.as_raw_hwnd());
let interop = windows::core::factory::<GraphicsCaptureItem, IGraphicsCaptureItemInterop>()?;
let item = unsafe { interop.CreateForWindow(window)? };
Ok(GraphicsCaptureItemType::Window((item, self)))
}
}