use std::mem;
use image::RgbaImage;
use scopeguard::{ScopeGuard, guard};
use widestring::U16CString;
use windows::{
Win32::{
Devices::Display::{
DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME, DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME,
DISPLAYCONFIG_DEVICE_INFO_HEADER, DISPLAYCONFIG_MODE_INFO, DISPLAYCONFIG_PATH_INFO,
DISPLAYCONFIG_SOURCE_DEVICE_NAME, DISPLAYCONFIG_TARGET_DEVICE_NAME,
DisplayConfigGetDeviceInfo, GetDisplayConfigBufferSizes, QDC_ONLY_ACTIVE_PATHS,
QueryDisplayConfig,
},
Foundation::{CloseHandle, FreeLibrary, GetLastError, HANDLE, HMODULE, HWND},
Graphics::Gdi::MONITORINFOEXW,
System::{
LibraryLoader::{GetProcAddress, LoadLibraryW},
Registry::{HKEY_LOCAL_MACHINE, RRF_RT_REG_SZ, RegGetValueW},
Threading::{OpenProcess, PROCESS_ACCESS_RIGHTS},
},
UI::WindowsAndMessaging::{GetWindowInfo, WINDOWINFO},
},
core::{HRESULT, PCWSTR, s, w},
};
use crate::{XCapError, error::XCapResult};
pub(super) fn get_build_number() -> u32 {
unsafe {
let mut buf_len: u32 = 2048;
let mut buf: Vec<u16> = Vec::with_capacity(buf_len as usize);
let err = RegGetValueW(
HKEY_LOCAL_MACHINE,
w!(r"SOFTWARE\Microsoft\Windows NT\CurrentVersion"),
w!("CurrentBuildNumber"),
RRF_RT_REG_SZ,
None,
Some(buf.as_mut_ptr().cast()),
Some(&mut buf_len),
);
if err.is_err() {
return 0;
}
buf.set_len(buf_len as usize);
let build_version = U16CString::from_vec_truncate(buf)
.to_string()
.unwrap_or_default();
build_version.parse().unwrap_or(0)
}
}
pub(super) fn get_os_major_version() -> u8 {
let build_number = get_build_number();
if build_number >= 22000 {
11
} else if build_number >= 10240 {
10
} else if build_number >= 9200 {
8
} else {
7
}
}
pub(super) fn bgra_to_rgba(mut buffer: Vec<u8>) -> Vec<u8> {
let is_old_version = get_os_major_version() < 8;
for src in buffer.chunks_exact_mut(4) {
src.swap(0, 2);
if src[3] == 0 && is_old_version {
src[3] = 255;
}
}
buffer
}
pub(super) fn bgra_to_rgba_image(
width: u32,
height: u32,
buffer: Vec<u8>,
) -> XCapResult<RgbaImage> {
RgbaImage::from_raw(width, height, bgra_to_rgba(buffer))
.ok_or_else(|| XCapError::new("RgbaImage::from_raw failed"))
}
type GetProcessDpiAwareness =
unsafe extern "system" fn(hprocess: HANDLE, value: *mut u32) -> HRESULT;
pub(super) fn get_process_is_dpi_awareness(process: HANDLE) -> XCapResult<bool> {
unsafe {
let scope_guard_hmodule = load_library(w!("Shcore.dll"))?;
let get_process_dpi_awareness_proc_address =
GetProcAddress(*scope_guard_hmodule, s!("GetProcessDpiAwareness")).ok_or(
XCapError::new("GetProcAddress GetProcessDpiAwareness failed"),
)?;
let get_process_dpi_awareness: GetProcessDpiAwareness =
mem::transmute(get_process_dpi_awareness_proc_address);
let mut process_dpi_awareness = 0;
get_process_dpi_awareness(process, &mut process_dpi_awareness).ok()?;
Ok(process_dpi_awareness != 0)
}
}
pub(super) fn load_library(
lib_filename: PCWSTR,
) -> XCapResult<ScopeGuard<HMODULE, impl FnOnce(HMODULE)>> {
unsafe {
let hmodule = LoadLibraryW(lib_filename)?;
if hmodule.is_invalid() {
return Err(XCapError::new(format!(
"LoadLibraryW error {:?}",
GetLastError()
)));
}
let scope_guard_hmodule = guard(hmodule, |val| {
if let Err(err) = FreeLibrary(val) {
log::error!("FreeLibrary {val:?} failed {err:?}");
}
});
Ok(scope_guard_hmodule)
}
}
pub(super) fn open_process(
dw_desired_access: PROCESS_ACCESS_RIGHTS,
b_inherit_handle: bool,
dw_process_id: u32,
) -> XCapResult<ScopeGuard<HANDLE, impl FnOnce(HANDLE)>> {
unsafe {
let handle = OpenProcess(dw_desired_access, b_inherit_handle, dw_process_id)?;
if handle.is_invalid() {
return Err(XCapError::new(format!(
"OpenProcess error {:?}",
GetLastError()
)));
}
let scope_guard_handle = guard(handle, |val| {
if let Err(err) = CloseHandle(val) {
log::error!("CloseHandle {val:?} failed {err:?}");
}
});
Ok(scope_guard_handle)
}
}
pub(super) fn get_monitor_config(
monitor_info_ex_w: MONITORINFOEXW,
) -> XCapResult<DISPLAYCONFIG_TARGET_DEVICE_NAME> {
unsafe {
let mut number_of_paths = 0;
let mut number_of_modes = 0;
GetDisplayConfigBufferSizes(
QDC_ONLY_ACTIVE_PATHS,
&mut number_of_paths,
&mut number_of_modes,
)
.ok()?;
let mut paths = vec![DISPLAYCONFIG_PATH_INFO::default(); number_of_paths as usize];
let mut modes = vec![DISPLAYCONFIG_MODE_INFO::default(); number_of_modes as usize];
QueryDisplayConfig(
QDC_ONLY_ACTIVE_PATHS,
&mut number_of_paths,
paths.as_mut_ptr(),
&mut number_of_modes,
modes.as_mut_ptr(),
None,
)
.ok()?;
for path in paths {
let mut source = DISPLAYCONFIG_SOURCE_DEVICE_NAME {
header: DISPLAYCONFIG_DEVICE_INFO_HEADER {
r#type: DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME,
size: mem::size_of::<DISPLAYCONFIG_SOURCE_DEVICE_NAME>() as u32,
adapterId: path.sourceInfo.adapterId,
id: path.sourceInfo.id,
},
..DISPLAYCONFIG_SOURCE_DEVICE_NAME::default()
};
if DisplayConfigGetDeviceInfo(&mut source.header) != 0 {
continue;
}
if source.viewGdiDeviceName != monitor_info_ex_w.szDevice {
continue;
}
let mut target = DISPLAYCONFIG_TARGET_DEVICE_NAME {
header: DISPLAYCONFIG_DEVICE_INFO_HEADER {
r#type: DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME,
size: mem::size_of::<DISPLAYCONFIG_TARGET_DEVICE_NAME>() as u32,
adapterId: path.sourceInfo.adapterId,
id: path.targetInfo.id,
},
..DISPLAYCONFIG_TARGET_DEVICE_NAME::default()
};
if DisplayConfigGetDeviceInfo(&mut target.header) != 0 {
continue;
}
return Ok(target);
}
Err(XCapError::new("Get monitor name failed"))
}
}
pub fn get_window_info(hwnd: HWND) -> XCapResult<WINDOWINFO> {
let mut window_info = WINDOWINFO {
cbSize: mem::size_of::<WINDOWINFO>() as u32,
..WINDOWINFO::default()
};
unsafe {
GetWindowInfo(hwnd, &mut window_info)?;
};
Ok(window_info)
}
#[cfg(test)]
mod tests {
use windows::Win32::Foundation::POINT;
use windows::Win32::Graphics::Gdi::{
DEVMODEW, ENUM_CURRENT_SETTINGS, EnumDisplaySettingsW, GetMonitorInfoW,
MONITOR_DEFAULTTOPRIMARY, MONITORINFO, MonitorFromPoint,
};
use windows::Win32::System::Threading::{
GetCurrentProcessId, PROCESS_QUERY_LIMITED_INFORMATION,
};
use windows::Win32::UI::WindowsAndMessaging::GetDesktopWindow;
use super::*;
#[test]
fn test_get_build_number() {
let build = get_build_number();
println!("build {build}");
assert!(build == 26100, "build number should be 26100");
}
#[test]
fn test_get_os_major_version() {
let version = get_os_major_version();
assert!(version == 11, "os major version should be 11");
}
#[test]
fn test_bgra_to_rgba() {
let input = vec![0, 1, 2, 255, 4, 5, 6, 255];
let output = bgra_to_rgba(input);
assert_eq!(output, vec![2, 1, 0, 255, 6, 5, 4, 255]);
}
#[test]
fn test_bgra_to_rgba_image() {
let width = 2;
let height = 1;
let buffer = vec![0, 1, 2, 255, 4, 5, 6, 255];
let result = bgra_to_rgba_image(width, height, buffer);
assert!(result.is_ok());
let image = result.unwrap();
assert_eq!(image.width(), width);
assert_eq!(image.height(), height);
}
#[test]
fn test_get_process_is_dpi_awareness() {
}
#[test]
fn test_load_library() {
let scope_guard_hmodule = load_library(w!("Shcore.dll")).unwrap();
assert!(!scope_guard_hmodule.is_invalid());
}
#[test]
fn test_open_process() {
unsafe {
let process_id = GetCurrentProcessId();
let scope_guard_handle =
open_process(PROCESS_QUERY_LIMITED_INFORMATION, true, process_id).unwrap();
assert!(!scope_guard_handle.is_invalid());
}
}
fn get_monitor_info_ex_w() -> MONITORINFOEXW {
unsafe {
let point = POINT { x: 0, y: 0 };
let h_monitor = MonitorFromPoint(point, MONITOR_DEFAULTTOPRIMARY);
let mut monitor_info_ex_w = MONITORINFOEXW::default();
monitor_info_ex_w.monitorInfo.cbSize = mem::size_of::<MONITORINFOEXW>() as u32;
let monitor_info_ex_w_ptr =
&mut monitor_info_ex_w as *mut MONITORINFOEXW as *mut MONITORINFO;
GetMonitorInfoW(h_monitor, monitor_info_ex_w_ptr)
.ok()
.unwrap();
monitor_info_ex_w
}
}
#[test]
fn test_get_monitor_config() {
let monitor_info_ex_w = get_monitor_info_ex_w();
let monitor_config = get_monitor_config(monitor_info_ex_w).unwrap();
assert!(!monitor_config.monitorFriendlyDeviceName.is_empty());
}
#[test]
fn test_get_window_info() {
unsafe {
let monitor_info_ex_w = get_monitor_info_ex_w();
let mut dev_mode_w = DEVMODEW {
dmSize: mem::size_of::<DEVMODEW>() as u16,
..DEVMODEW::default()
};
EnumDisplaySettingsW(
PCWSTR(monitor_info_ex_w.szDevice.as_ptr()),
ENUM_CURRENT_SETTINGS,
&mut dev_mode_w,
)
.ok()
.unwrap();
let hwnd = GetDesktopWindow();
let window_info = get_window_info(hwnd).unwrap();
let width = (window_info.rcWindow.right - window_info.rcWindow.left) as u32;
assert!(width == dev_mode_w.dmPelsWidth);
}
}
}