From 46918cd4cfc395ac924d0fe2aab2480d118ffb0c Mon Sep 17 00:00:00 2001 From: nashaofu <19303058+nashaofu@users.noreply.github.com> Date: Sun, 22 Dec 2024 19:58:04 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=20windows=20=E7=AA=97?= =?UTF-8?q?=E5=8F=A3=E6=88=AA=E5=9B=BE=20(#180)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 优化 windows 窗口截图 * chore: 更新版本号 * fix: fmt --------- Co-authored-by: nashaofu --- Cargo.toml | 2 +- examples/monitor_capture.rs | 6 +----- examples/window_capture.rs | 6 +----- src/windows/impl_monitor.rs | 29 ++++++++-------------------- src/windows/impl_window.rs | 30 +++++++++++++++++++++-------- src/windows/utils.rs | 38 ++++++++++++++++++++++++++++++++++--- 6 files changed, 68 insertions(+), 43 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bc088b5..72bf4a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "xcap" -version = "0.1.1" +version = "0.2.0" edition = "2021" description = "XCap is a cross-platform screen capture library written in Rust. It supports Linux (X11, Wayland), MacOS, and Windows. XCap supports screenshot and video recording (WIP)." license = "Apache-2.0" diff --git a/examples/monitor_capture.rs b/examples/monitor_capture.rs index c7c28f5..bb77d13 100644 --- a/examples/monitor_capture.rs +++ b/examples/monitor_capture.rs @@ -3,11 +3,7 @@ use std::time::Instant; use xcap::Monitor; fn normalized(filename: &str) -> String { - filename - .replace('|', "") - .replace('\\', "") - .replace(':', "") - .replace('/', "") + filename.replace(['|', '\\', ':', '/'], "") } fn main() { diff --git a/examples/window_capture.rs b/examples/window_capture.rs index fabaa8e..891173d 100644 --- a/examples/window_capture.rs +++ b/examples/window_capture.rs @@ -3,11 +3,7 @@ use std::time::Instant; use xcap::Window; fn normalized(filename: &str) -> String { - filename - .replace('|', "") - .replace('\\', "") - .replace(':', "") - .replace('/', "") + filename.replace(['|', '\\', ':', '/'], "") } fn main() { diff --git a/src/windows/impl_monitor.rs b/src/windows/impl_monitor.rs index d83da50..796dc90 100644 --- a/src/windows/impl_monitor.rs +++ b/src/windows/impl_monitor.rs @@ -4,14 +4,14 @@ use image::RgbaImage; use windows::{ core::{s, w, HRESULT, PCWSTR}, Win32::{ - Foundation::{BOOL, HANDLE, LPARAM, POINT, RECT, TRUE}, + Foundation::{BOOL, LPARAM, POINT, RECT, TRUE}, Graphics::Gdi::{ EnumDisplayMonitors, EnumDisplaySettingsW, GetDeviceCaps, GetMonitorInfoW, MonitorFromPoint, DESKTOPHORZRES, DEVMODEW, DMDO_180, DMDO_270, DMDO_90, DMDO_DEFAULT, ENUM_CURRENT_SETTINGS, HDC, HMONITOR, HORZRES, MONITORINFO, MONITORINFOEXW, MONITOR_DEFAULTTONULL, }, - System::LibraryLoader::GetProcAddress, + System::{LibraryLoader::GetProcAddress, Threading::GetCurrentProcess}, UI::WindowsAndMessaging::MONITORINFOF_PRIMARY, }, }; @@ -22,7 +22,7 @@ use super::{ boxed::{BoxHDC, BoxHModule}, capture::capture_monitor, impl_video_recorder::ImplVideoRecorder, - utils::wide_string_to_string, + utils::{get_process_is_dpi_awareness, wide_string_to_string}, }; // A 函数与 W 函数区别 @@ -74,10 +74,6 @@ fn get_dev_mode_w(monitor_info_exw: &MONITORINFOEXW) -> XCapResult { Ok(dev_mode_w) } -// 定义 GetProcessDpiAwareness 函数的类型 -type GetProcessDpiAwareness = - unsafe extern "system" fn(hprocess: HANDLE, value: *mut u32) -> HRESULT; - // 定义 GetDpiForMonitor 函数的类型 type GetDpiForMonitor = unsafe extern "system" fn( hmonitor: HMONITOR, @@ -88,25 +84,16 @@ type GetDpiForMonitor = unsafe extern "system" fn( fn get_hi_dpi_scale_factor(hmonitor: HMONITOR) -> XCapResult { unsafe { - let box_hmodule = BoxHModule::new(w!("Shcore.dll"))?; - - let get_get_process_dpi_awareness_proc_address = - GetProcAddress(*box_hmodule, s!("GetProcessDpiAwareness")).ok_or(XCapError::new( - "GetProcAddress GetProcessDpiAwareness failed", - ))?; - - let get_get_process_dpi_awareness: GetProcessDpiAwareness = - mem::transmute(get_get_process_dpi_awareness_proc_address); - - let mut process_dpi_awareness = 0; - // https://learn.microsoft.com/zh-cn/windows/win32/api/shellscalingapi/nf-shellscalingapi-getprocessdpiawareness - get_get_process_dpi_awareness(HANDLE::default(), &mut process_dpi_awareness).ok()?; + let current_process_is_dpi_awareness: bool = + get_process_is_dpi_awareness(GetCurrentProcess())?; // 当前进程不感知 DPI,则回退到 GetDeviceCaps 获取 DPI - if process_dpi_awareness == 0 { + if !current_process_is_dpi_awareness { return Err(XCapError::new("Process not DPI aware")); } + let box_hmodule = BoxHModule::new(w!("Shcore.dll"))?; + let get_dpi_for_monitor_proc_address = GetProcAddress(*box_hmodule, s!("GetDpiForMonitor")) .ok_or(XCapError::new("GetProcAddress GetDpiForMonitor failed"))?; diff --git a/src/windows/impl_window.rs b/src/windows/impl_window.rs index 7fac34b..c0ed62a 100644 --- a/src/windows/impl_window.rs +++ b/src/windows/impl_window.rs @@ -12,7 +12,7 @@ use windows::{ Storage::FileSystem::{GetFileVersionInfoSizeW, GetFileVersionInfoW, VerQueryValueW}, System::{ ProcessStatus::{GetModuleBaseNameW, GetModuleFileNameExW}, - Threading::{GetCurrentProcessId, PROCESS_ALL_ACCESS}, + Threading::{GetCurrentProcess, GetCurrentProcessId, PROCESS_ALL_ACCESS}, }, UI::WindowsAndMessaging::{ EnumWindows, GetClassNameW, GetWindowInfo, GetWindowLongPtrW, GetWindowTextLengthW, @@ -27,7 +27,11 @@ use crate::{ platform::{boxed::BoxProcessHandle, utils::log_last_error}, }; -use super::{capture::capture_window, impl_monitor::ImplMonitor, utils::wide_string_to_string}; +use super::{ + capture::capture_window, + impl_monitor::ImplMonitor, + utils::{get_process_is_dpi_awareness, wide_string_to_string}, +}; #[derive(Debug, Clone)] pub(crate) struct ImplWindow { @@ -368,11 +372,21 @@ impl ImplWindow { impl ImplWindow { pub fn capture_image(&self) -> XCapResult { - // TODO: 在win10之后,不同窗口有不同的dpi,所以可能存在截图不全或者截图有较大空白,实际窗口没有填充满图片 - capture_window( - self.hwnd, - self.current_monitor.scale_factor, - &self.window_info, - ) + // 在win10之后,不同窗口有不同的dpi,所以可能存在截图不全或者截图有较大空白,实际窗口没有填充满图片 + // 如果窗口不感知dpi,那么就不需要缩放,如果当前进程感知dpi,那么也不需要缩放 + let box_process_handle = BoxProcessHandle::open(PROCESS_ALL_ACCESS, false, self.pid)?; + let window_is_dpi_awareness = get_process_is_dpi_awareness(*box_process_handle)?; + let current_process_is_dpi_awareness = + unsafe { get_process_is_dpi_awareness(GetCurrentProcess())? }; + + let scale_factor = if !window_is_dpi_awareness { + 1.0 + } else if current_process_is_dpi_awareness { + 1.0 + } else { + self.current_monitor.scale_factor + }; + + capture_window(self.hwnd, scale_factor, &self.window_info) } } diff --git a/src/windows/utils.rs b/src/windows/utils.rs index 07e780e..8ad77e0 100644 --- a/src/windows/utils.rs +++ b/src/windows/utils.rs @@ -1,14 +1,21 @@ +use std::mem; + use image::RgbaImage; use windows::{ - core::w, + core::{s, w, HRESULT}, Win32::{ - Foundation::GetLastError, - System::Registry::{RegGetValueW, HKEY_LOCAL_MACHINE, RRF_RT_REG_SZ}, + Foundation::{GetLastError, HANDLE}, + System::{ + LibraryLoader::GetProcAddress, + Registry::{RegGetValueW, HKEY_LOCAL_MACHINE, RRF_RT_REG_SZ}, + }, }, }; use crate::{error::XCapResult, XCapError}; +use super::boxed::BoxHModule; + pub(super) fn wide_string_to_string(wide_string: &[u16]) -> XCapResult { let string = if let Some(null_pos) = wide_string.iter().position(|pos| *pos == 0) { String::from_utf16(&wide_string[..null_pos])? @@ -88,3 +95,28 @@ pub(super) fn bgra_to_rgba_image( RgbaImage::from_raw(width, height, bgra_to_rgba(buffer)) .ok_or_else(|| XCapError::new("RgbaImage::from_raw failed")) } + +// 定义 GetProcessDpiAwareness 函数的类型 +type GetProcessDpiAwareness = + unsafe extern "system" fn(hprocess: HANDLE, value: *mut u32) -> HRESULT; + +pub(super) fn get_process_is_dpi_awareness(process: HANDLE) -> XCapResult { + unsafe { + let box_hmodule = BoxHModule::new(w!("Shcore.dll"))?; + + let get_process_dpi_awareness_proc_address = + GetProcAddress(*box_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; + // https://learn.microsoft.com/zh-cn/windows/win32/api/shellscalingapi/nf-shellscalingapi-getprocessdpiawareness + get_process_dpi_awareness(process, &mut process_dpi_awareness).ok()?; + + // 当前进程不感知 DPI,则回退到 GetDeviceCaps 获取 DPI + Ok(process_dpi_awareness != 0) + } +}