Skip to content

Commit

Permalink
fix: 优化 windows 窗口截图 (#180)
Browse files Browse the repository at this point in the history
* fix: 优化 windows 窗口截图

* chore: 更新版本号

* fix: fmt

---------

Co-authored-by: nashaofu <[email protected]>
  • Loading branch information
nashaofu and nashaofu authored Dec 22, 2024
1 parent 5457eb2 commit 46918cd
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 43 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
6 changes: 1 addition & 5 deletions examples/monitor_capture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
6 changes: 1 addition & 5 deletions examples/window_capture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
29 changes: 8 additions & 21 deletions src/windows/impl_monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
};
Expand All @@ -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 函数区别
Expand Down Expand Up @@ -74,10 +74,6 @@ fn get_dev_mode_w(monitor_info_exw: &MONITORINFOEXW) -> XCapResult<DEVMODEW> {
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,
Expand All @@ -88,25 +84,16 @@ type GetDpiForMonitor = unsafe extern "system" fn(

fn get_hi_dpi_scale_factor(hmonitor: HMONITOR) -> XCapResult<f32> {
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"))?;

Expand Down
30 changes: 22 additions & 8 deletions src/windows/impl_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -368,11 +372,21 @@ impl ImplWindow {

impl ImplWindow {
pub fn capture_image(&self) -> XCapResult<RgbaImage> {
// 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)
}
}
38 changes: 35 additions & 3 deletions src/windows/utils.rs
Original file line number Diff line number Diff line change
@@ -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<String> {
let string = if let Some(null_pos) = wide_string.iter().position(|pos| *pos == 0) {
String::from_utf16(&wide_string[..null_pos])?
Expand Down Expand Up @@ -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<bool> {
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)
}
}

0 comments on commit 46918cd

Please sign in to comment.