diff --git a/Cargo.toml b/Cargo.toml index 59251ae..7f512b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "xcap" -version = "0.2.2" +version = "0.3.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" @@ -18,22 +18,23 @@ image = ["image/default"] [dependencies] image = { version = "0.25", default-features = false, features = ["png"] } log = "0.4" +scopeguard = "1.2" thiserror = "2.0" [target.'cfg(target_os = "macos")'.dependencies] -core-foundation = "0.10" -core-graphics = "0.24" -objc2-app-kit = { version = "0.2.2", features = [ - "libc", - "NSWorkspace", - "NSRunningApplication", -] } +objc2 = "0.6" +objc2-app-kit = "0.3" +objc2-core-foundation = "0.3" +objc2-core-graphics = "0.3" +objc2-foundation = "0.3" [target.'cfg(target_os = "windows")'.dependencies] -windows = { version = "0.58", features = [ +widestring = "1.1" +windows = { version = "0.59", features = [ "Win32_Foundation", "Win32_Graphics_Gdi", "Win32_Graphics_Dwm", + "Win32_Devices_Display", "Win32_System_LibraryLoader", "Win32_UI_WindowsAndMessaging", "Win32_Storage_Xps", diff --git a/src/error.rs b/src/error.rs index cc565f1..49ea772 100644 --- a/src/error.rs +++ b/src/error.rs @@ -32,15 +32,15 @@ pub enum XCapError { StdTimeSystemTimeError(#[from] std::time::SystemTimeError), #[cfg(target_os = "macos")] - #[error("CoreGraphicsDisplayCGError {0}")] - CoreGraphicsDisplayCGError(core_graphics::display::CGError), + #[error("Objc2CoreGraphicsCGError {:?}", 0)] + Objc2CoreGraphicsCGError(objc2_core_graphics::CGError), #[cfg(target_os = "windows")] #[error(transparent)] WindowsCoreError(#[from] windows::core::Error), #[cfg(target_os = "windows")] #[error(transparent)] - StdStringFromUtf16Error(#[from] std::string::FromUtf16Error), + Utf16Error(#[from] widestring::error::Utf16Error), } impl XCapError { @@ -49,12 +49,12 @@ impl XCapError { } } -#[cfg(target_os = "macos")] -impl From for XCapError { - fn from(value: core_graphics::display::CGError) -> Self { - XCapError::CoreGraphicsDisplayCGError(value) - } -} +// #[cfg(target_os = "macos")] +// impl From for XCapError { +// fn from(value: core_graphics::display::CGError) -> Self { +// XCapError::CoreGraphicsDisplayCGError(value) +// } +// } pub type XCapResult = Result; diff --git a/src/macos/boxed.rs b/src/macos/boxed.rs deleted file mode 100644 index aae49e0..0000000 --- a/src/macos/boxed.rs +++ /dev/null @@ -1,31 +0,0 @@ -use core_foundation::{ - array::CFArrayRef, - base::{CFRelease, ToVoid}, -}; -use std::ops::Deref; - -#[derive(Debug)] -pub(super) struct BoxCFArrayRef { - cf_array_ref: CFArrayRef, -} - -impl Deref for BoxCFArrayRef { - type Target = CFArrayRef; - fn deref(&self) -> &Self::Target { - &self.cf_array_ref - } -} - -impl Drop for BoxCFArrayRef { - fn drop(&mut self) { - unsafe { - CFRelease(self.cf_array_ref.to_void()); - } - } -} - -impl BoxCFArrayRef { - pub fn new(cf_array_ref: CFArrayRef) -> Self { - BoxCFArrayRef { cf_array_ref } - } -} diff --git a/src/macos/capture.rs b/src/macos/capture.rs index abe5bbc..ef46a8a 100644 --- a/src/macos/capture.rs +++ b/src/macos/capture.rs @@ -1,9 +1,9 @@ -use core_graphics::{ - display::{kCGWindowImageDefault, CGWindowID, CGWindowListOption}, - geometry::CGRect, - window::create_image, -}; use image::RgbaImage; +use objc2_core_foundation::CGRect; +use objc2_core_graphics::{ + CGDataProviderCopyData, CGImageGetBytesPerRow, CGImageGetDataProvider, CGImageGetHeight, + CGImageGetWidth, CGWindowID, CGWindowImageOption, CGWindowListCreateImage, CGWindowListOption, +}; use crate::error::{XCapError, XCapResult}; @@ -12,26 +12,36 @@ pub fn capture( list_option: CGWindowListOption, window_id: CGWindowID, ) -> XCapResult { - let cg_image = create_image(cg_rect, list_option, window_id, kCGWindowImageDefault) - .ok_or_else(|| XCapError::new(format!("Capture failed {} {:?}", window_id, cg_rect)))?; + unsafe { + let cg_image = CGWindowListCreateImage( + cg_rect, + list_option, + window_id, + CGWindowImageOption::Default, + ); - let width = cg_image.width(); - let height = cg_image.height(); - let bytes = Vec::from(cg_image.data().bytes()); + let width = CGImageGetWidth(cg_image.as_deref()); + let height = CGImageGetHeight(cg_image.as_deref()); + let data_provider = CGImageGetDataProvider(cg_image.as_deref()); + let data = CGDataProviderCopyData(data_provider.as_deref()) + .ok_or_else(|| XCapError::new("Failed to copy data"))? + .to_vec(); + let bytes_per_row = CGImageGetBytesPerRow(cg_image.as_deref()); - // Some platforms e.g. MacOS can have extra bytes at the end of each row. - // See - // https://github.com/nashaofu/xcap/issues/29 - // https://github.com/nashaofu/xcap/issues/38 - let mut buffer = Vec::with_capacity(width * height * 4); - for row in bytes.chunks_exact(cg_image.bytes_per_row()) { - buffer.extend_from_slice(&row[..width * 4]); - } + // Some platforms e.g. MacOS can have extra bytes at the end of each row. + // See + // https://github.com/nashaofu/xcap/issues/29 + // https://github.com/nashaofu/xcap/issues/38 + let mut buffer = Vec::with_capacity(width * height * 4); + for row in data.chunks_exact(bytes_per_row) { + buffer.extend_from_slice(&row[..width * 4]); + } - for bgra in buffer.chunks_exact_mut(4) { - bgra.swap(0, 2); - } + for bgra in buffer.chunks_exact_mut(4) { + bgra.swap(0, 2); + } - RgbaImage::from_raw(width as u32, height as u32, buffer) - .ok_or_else(|| XCapError::new("RgbaImage::from_raw failed")) + RgbaImage::from_raw(width as u32, height as u32, buffer) + .ok_or_else(|| XCapError::new("RgbaImage::from_raw failed")) + } } diff --git a/src/macos/impl_monitor.rs b/src/macos/impl_monitor.rs index 595bf94..9ca8404 100644 --- a/src/macos/impl_monitor.rs +++ b/src/macos/impl_monitor.rs @@ -1,8 +1,13 @@ -use core_graphics::display::{ - kCGNullWindowID, kCGWindowListOptionAll, CGDirectDisplayID, CGDisplay, CGDisplayMode, CGError, - CGPoint, -}; use image::RgbaImage; +use objc2::MainThreadMarker; +use objc2_app_kit::NSScreen; +use objc2_core_foundation::{CGPoint, CGRect}; +use objc2_core_graphics::{ + CGDirectDisplayID, CGDisplayBounds, CGDisplayCopyDisplayMode, CGDisplayIsActive, + CGDisplayIsMain, CGDisplayModeGetPixelWidth, CGDisplayModeGetRefreshRate, CGDisplayRotation, + CGError, CGGetActiveDisplayList, CGGetDisplaysWithPoint, CGWindowListOption, +}; +use objc2_foundation::{NSNumber, NSString}; use crate::error::{XCapError, XCapResult}; @@ -10,7 +15,7 @@ use super::{capture::capture, impl_video_recorder::ImplVideoRecorder}; #[derive(Debug, Clone)] pub(crate) struct ImplMonitor { - pub cg_display: CGDisplay, + pub cg_direct_display_id: CGDirectDisplayID, pub id: u32, pub name: String, pub x: i32, @@ -23,53 +28,90 @@ pub(crate) struct ImplMonitor { pub is_primary: bool, } -#[link(name = "CoreGraphics", kind = "framework")] -extern "C" { - fn CGGetDisplaysWithPoint( - point: CGPoint, - max_displays: u32, - displays: *mut CGDirectDisplayID, - display_count: *mut u32, - ) -> CGError; +fn get_display_friendly_name(display_id: CGDirectDisplayID) -> XCapResult { + let screens = NSScreen::screens(unsafe { MainThreadMarker::new_unchecked() }); + for screen in screens { + let device_description = screen.deviceDescription(); + let screen_number = device_description + .objectForKey(&NSString::from_str("NSScreenNumber")) + .ok_or(XCapError::new("Get NSScreenNumber failed"))?; + + let screen_id = screen_number + .downcast::() + .map_err(|err| XCapError::new(format!("{:?}", err)))? + .unsignedIntValue(); + + if screen_id == display_id { + unsafe { return Ok(screen.localizedName().to_string()) }; + } + } + + Err(XCapError::new(format!( + "Get display {} friendly name failed", + display_id + ))) } impl ImplMonitor { pub(super) fn new(id: CGDirectDisplayID) -> XCapResult { - let cg_display = CGDisplay::new(id); - let screen_num = cg_display.model_number(); - let cg_rect = cg_display.bounds(); - let cg_display_mode = get_cg_display_mode(cg_display)?; - let pixel_width = cg_display_mode.pixel_width(); - let scale_factor = pixel_width as f32 / cg_rect.size.width as f32; - - Ok(ImplMonitor { - cg_display, - id: cg_display.id, - name: format!("Monitor #{screen_num}"), - x: cg_rect.origin.x as i32, - y: cg_rect.origin.y as i32, - width: cg_rect.size.width as u32, - height: cg_rect.size.height as u32, - rotation: cg_display.rotation() as f32, - scale_factor, - frequency: cg_display_mode.refresh_rate() as f32, - is_primary: cg_display.is_main(), - }) + unsafe { + let CGRect { origin, size } = CGDisplayBounds(id); + + let rotation = CGDisplayRotation(id) as f32; + + let display_mode = CGDisplayCopyDisplayMode(id); + let pixel_width = CGDisplayModeGetPixelWidth(display_mode.as_deref()); + let scale_factor = pixel_width as f32 / size.width as f32; + let frequency = CGDisplayModeGetRefreshRate(display_mode.as_deref()) as f32; + let is_primary = CGDisplayIsMain(id); + + Ok(ImplMonitor { + cg_direct_display_id: id, + id, + name: get_display_friendly_name(id).unwrap_or(format!("Unknown Monitor {}", id)), + x: origin.x as i32, + y: origin.y as i32, + width: size.width as u32, + height: size.height as u32, + rotation, + scale_factor, + frequency, + is_primary, + }) + } } pub fn all() -> XCapResult> { - // active vs online https://developer.apple.com/documentation/coregraphics/1454964-cggetonlinedisplaylist?language=objc - let display_ids = CGDisplay::active_displays()?; + let max_displays: u32 = 16; + let mut active_displays: Vec = vec![0; max_displays as usize]; + let mut display_count: u32 = 0; - let mut impl_monitors: Vec = Vec::with_capacity(display_ids.len()); + let cg_error = unsafe { + CGGetActiveDisplayList( + max_displays, + active_displays.as_mut_ptr(), + &mut display_count, + ) + }; - for display_id in display_ids { + if cg_error != CGError::Success { + return Err(XCapError::new(format!( + "CGGetActiveDisplayList failed: {:?}", + cg_error + ))); + } + + active_displays.truncate(display_count as usize); + + let mut impl_monitors = Vec::with_capacity(active_displays.len()); + + for display in active_displays { // 运行过程中,如果遇到显示器插拔,可能会导致调用报错 // 对于报错的情况,就把报错的情况给排除掉 // https://github.com/nashaofu/xcap/issues/118 - if let Ok(impl_monitor) = ImplMonitor::new(display_id) { + if let Ok(impl_monitor) = ImplMonitor::new(display) { impl_monitors.push(impl_monitor); } else { - log::error!("ImplMonitor::new({}) failed", display_id); + log::error!("ImplMonitor::new({}) failed", display); } } @@ -81,6 +123,7 @@ impl ImplMonitor { x: x as f64, y: y as f64, }; + let max_displays: u32 = 16; let mut display_ids: Vec = vec![0; max_displays as usize]; let mut display_count: u32 = 0; @@ -94,43 +137,29 @@ impl ImplMonitor { ) }; - if cg_error != 0 { - return Err(XCapError::CoreGraphicsDisplayCGError(cg_error)); + if cg_error != CGError::Success { + return Err(XCapError::new(format!( + "CGGetDisplaysWithPoint failed: {:?}", + cg_error + ))); } - if display_count == 0 { - return Err(XCapError::new("Get displays from point failed")); - } - - let display_id = display_ids - .first() - .ok_or(XCapError::new("Monitor not found"))?; - - let impl_monitor = ImplMonitor::new(*display_id)?; - - if !impl_monitor.cg_display.is_active() { - Err(XCapError::new("Monitor is not active")) + if let Some(&display_id) = display_ids.first() { + if unsafe { !CGDisplayIsActive(display_id) } { + return Err(XCapError::new("Monitor is not active")); + } + ImplMonitor::new(display_id) } else { - Ok(impl_monitor) + Err(XCapError::new("Monitor not found")) } } } -fn get_cg_display_mode(cg_display: CGDisplay) -> XCapResult { - let cg_display_mode = cg_display - .display_mode() - .ok_or_else(|| XCapError::new("Get display mode failed"))?; - - Ok(cg_display_mode) -} - impl ImplMonitor { pub fn capture_image(&self) -> XCapResult { - capture( - self.cg_display.bounds(), - kCGWindowListOptionAll, - kCGNullWindowID, - ) + let cg_rect = unsafe { CGDisplayBounds(self.cg_direct_display_id) }; + + capture(cg_rect, CGWindowListOption::OptionAll, 0) } pub fn video_recorder(&self) -> XCapResult { diff --git a/src/macos/impl_window.rs b/src/macos/impl_window.rs index fec2093..57fd920 100644 --- a/src/macos/impl_window.rs +++ b/src/macos/impl_window.rs @@ -1,26 +1,20 @@ use std::ffi::c_void; -use core_foundation::{ - array::{CFArrayGetCount, CFArrayGetValueAtIndex}, - base::{FromVoid, TCFType}, - dictionary::{CFDictionaryGetValue, CFDictionaryRef}, - number::{kCFNumberIntType, CFBooleanGetValue, CFBooleanRef, CFNumberGetValue, CFNumberRef}, - string::CFString, -}; -use core_graphics::{ - display::{ - kCGWindowListExcludeDesktopElements, kCGWindowListOptionIncludingWindow, - kCGWindowListOptionOnScreenOnly, CGDisplay, CGPoint, CGSize, CGWindowListCopyWindowInfo, - }, - geometry::CGRect, - window::{kCGNullWindowID, kCGWindowSharingNone}, -}; use image::RgbaImage; use objc2_app_kit::NSWorkspace; +use objc2_core_foundation::{ + CFArrayGetCount, CFArrayGetValueAtIndex, CFBoolean, CFBooleanGetValue, CFDictionary, + CFDictionaryGetValue, CFNumber, CFNumberGetValue, CFNumberType, CFString, CGPoint, CGRect, + CGSize, +}; +use objc2_core_graphics::{ + CGDisplayBounds, CGMainDisplayID, CGRectContainsPoint, CGRectIntersectsRect, + CGRectMakeWithDictionaryRepresentation, CGWindowListCopyWindowInfo, CGWindowListOption, +}; use crate::{error::XCapResult, XCapError}; -use super::{boxed::BoxCFArrayRef, capture::capture, impl_monitor::ImplMonitor}; +use super::{capture::capture, impl_monitor::ImplMonitor}; #[derive(Debug, Clone)] pub(crate) struct ImplWindow { @@ -41,22 +35,15 @@ pub(crate) struct ImplWindow { unsafe impl Send for ImplWindow {} -#[link(name = "CoreGraphics", kind = "framework")] -extern "C" { - fn CGRectMakeWithDictionaryRepresentation( - dict: CFDictionaryRef, - rect: &mut CGRect, - ) -> CFBooleanRef; -} - fn get_cf_dictionary_get_value( - cf_dictionary_ref: CFDictionaryRef, + cf_dictionary: &CFDictionary, key: &str, ) -> XCapResult<*const c_void> { unsafe { - let cf_dictionary_key = CFString::new(key); + let cf_dictionary_key = CFString::from_str(key); + let cf_dictionary_key_ref = cf_dictionary_key.as_ref() as *const CFString; - let value = CFDictionaryGetValue(cf_dictionary_ref, cf_dictionary_key.as_CFTypeRef()); + let value = CFDictionaryGetValue(cf_dictionary, cf_dictionary_key_ref.cast()); if value.is_null() { return Err(XCapError::new(format!( @@ -69,14 +56,14 @@ fn get_cf_dictionary_get_value( } } -fn get_cf_number_i32_value(cf_dictionary_ref: CFDictionaryRef, key: &str) -> XCapResult { +fn get_cf_number_i32_value(cf_dictionary: &CFDictionary, key: &str) -> XCapResult { unsafe { - let cf_number_ref = get_cf_dictionary_get_value(cf_dictionary_ref, key)?; + let cf_number = get_cf_dictionary_get_value(cf_dictionary, key)? as *const CFNumber; let mut value: i32 = 0; let is_success = CFNumberGetValue( - cf_number_ref as CFNumberRef, - kCFNumberIntType, + &*cf_number, + CFNumberType::IntType, &mut value as *mut _ as *mut c_void, ); @@ -91,30 +78,29 @@ fn get_cf_number_i32_value(cf_dictionary_ref: CFDictionaryRef, key: &str) -> XCa } } -fn get_cf_string_value(cf_dictionary_ref: CFDictionaryRef, key: &str) -> XCapResult { - let value_ref = get_cf_dictionary_get_value(cf_dictionary_ref, key)?; - - Ok(unsafe { CFString::from_void(value_ref).to_string() }) +fn get_cf_string_value(cf_dictionary: &CFDictionary, key: &str) -> XCapResult { + let value_ref = get_cf_dictionary_get_value(cf_dictionary, key)? as *const CFString; + let value = unsafe { (*value_ref).to_string() }; + Ok(value) } -fn get_cf_bool_value(cf_dictionary_ref: CFDictionaryRef, key: &str) -> XCapResult { - let value_ref = get_cf_dictionary_get_value(cf_dictionary_ref, key)?; +fn get_cf_bool_value(cf_dictionary: &CFDictionary, key: &str) -> XCapResult { + let value_ref = get_cf_dictionary_get_value(cf_dictionary, key)? as *const CFBoolean; - Ok(unsafe { CFBooleanGetValue(value_ref as CFBooleanRef) }) + Ok(unsafe { CFBooleanGetValue(&*value_ref) }) } -fn get_window_cg_rect(window_cf_dictionary_ref: CFDictionaryRef) -> XCapResult { +fn get_window_cg_rect(window_cf_dictionary: &CFDictionary) -> XCapResult { unsafe { - let window_bounds_ref = - get_cf_dictionary_get_value(window_cf_dictionary_ref, "kCGWindowBounds")? - as CFDictionaryRef; + let window_bounds = get_cf_dictionary_get_value(window_cf_dictionary, "kCGWindowBounds")? + as *const CFDictionary; let mut cg_rect = CGRect::default(); - let is_success_ref = - CGRectMakeWithDictionaryRepresentation(window_bounds_ref, &mut cg_rect); + let is_success = + CGRectMakeWithDictionaryRepresentation(Some(&*window_bounds), &mut cg_rect); - if is_success_ref.is_null() { + if !is_success { return Err(XCapError::new( "CGRectMakeWithDictionaryRepresentation failed", )); @@ -126,19 +112,19 @@ fn get_window_cg_rect(window_cf_dictionary_ref: CFDictionaryRef) -> XCapResult, ) -> XCapResult { - let id = get_cf_number_i32_value(window_cf_dictionary_ref, "kCGWindowNumber")? as u32; - let pid = get_cf_number_i32_value(window_cf_dictionary_ref, "kCGWindowOwnerPID")?; + let id = get_cf_number_i32_value(window_cf_dictionary, "kCGWindowNumber")? as u32; + let pid = get_cf_number_i32_value(window_cf_dictionary, "kCGWindowOwnerPID")?; - let cg_rect = get_window_cg_rect(window_cf_dictionary_ref)?; + let cg_rect = get_window_cg_rect(window_cf_dictionary)?; - let primary_monitor = ImplMonitor::new(CGDisplay::main().id)?; + let primary_monitor = ImplMonitor::new(unsafe { CGMainDisplayID() })?; let (is_maximized, current_monitor) = { // 获取窗口中心点的坐标 @@ -151,9 +137,10 @@ impl ImplWindow { let impl_monitor = impl_monitors .iter() - .find(|impl_monitor| { - let display_bounds = impl_monitor.cg_display.bounds(); - display_bounds.contains(&cg_point) || display_bounds.is_intersects(&cg_rect) + .find(|impl_monitor| unsafe { + let display_bounds = CGDisplayBounds(impl_monitor.cg_direct_display_id); + CGRectContainsPoint(display_bounds, cg_point) + || CGRectIntersectsRect(display_bounds, cg_rect) }) .unwrap_or(&primary_monitor); @@ -165,7 +152,7 @@ impl ImplWindow { }; let is_minimized = - !get_cf_bool_value(window_cf_dictionary_ref, "kCGWindowIsOnscreen")? && !is_maximized; + !get_cf_bool_value(window_cf_dictionary, "kCGWindowIsOnscreen")? && !is_maximized; let is_focused = focused_app_pid.eq(&Some(pid)); @@ -198,33 +185,33 @@ impl ImplWindow { // CGWindowListCopyWindowInfo 返回窗口顺序为从顶层到最底层 // 即在前面的窗口在数组前面 - let box_cf_array_ref = BoxCFArrayRef::new(CGWindowListCopyWindowInfo( - kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, - kCGNullWindowID, - )); - - if box_cf_array_ref.is_null() { - return Ok(impl_windows); - } + let cf_array = match CGWindowListCopyWindowInfo( + CGWindowListOption::OptionOnScreenOnly | CGWindowListOption::ExcludeDesktopElements, + 0, + ) { + Some(cf_array) => cf_array, + None => return Ok(impl_windows), + }; - let num_windows = CFArrayGetCount(*box_cf_array_ref); + let num_windows = CFArrayGetCount(&cf_array); for i in 0..num_windows { let window_cf_dictionary_ref = - CFArrayGetValueAtIndex(*box_cf_array_ref, i) as CFDictionaryRef; + CFArrayGetValueAtIndex(&cf_array, i) as *const CFDictionary; if window_cf_dictionary_ref.is_null() { continue; } - let window_name = - match get_cf_string_value(window_cf_dictionary_ref, "kCGWindowName") { - Ok(window_name) => window_name, - _ => continue, - }; + let window_cf_dictionary = &*window_cf_dictionary_ref; + + let window_name = match get_cf_string_value(window_cf_dictionary, "kCGWindowName") { + Ok(window_name) => window_name, + _ => continue, + }; let window_owner_name = - match get_cf_string_value(window_cf_dictionary_ref, "kCGWindowOwnerName") { + match get_cf_string_value(window_cf_dictionary, "kCGWindowOwnerName") { Ok(window_owner_name) => window_owner_name, _ => continue, }; @@ -233,20 +220,18 @@ impl ImplWindow { continue; } - let window_sharing_state = match get_cf_number_i32_value( - window_cf_dictionary_ref, - "kCGWindowSharingState", - ) { - Ok(window_sharing_state) => window_sharing_state as u32, - _ => continue, - }; + let window_sharing_state = + match get_cf_number_i32_value(window_cf_dictionary, "kCGWindowSharingState") { + Ok(window_sharing_state) => window_sharing_state as u32, + _ => continue, + }; - if window_sharing_state == kCGWindowSharingNone { + if window_sharing_state == 0 { continue; } if let Ok(impl_window) = ImplWindow::new( - window_cf_dictionary_ref, + window_cf_dictionary, &impl_monitors, window_name.clone(), window_owner_name.clone(), @@ -257,7 +242,7 @@ impl ImplWindow { } else { log::error!( "ImplWindow::new({:?}, {:?}, {:?}, {:?}) failed", - window_cf_dictionary_ref, + window_cf_dictionary, &impl_monitors, &window_name, &window_owner_name @@ -274,10 +259,10 @@ impl ImplWindow { pub fn capture_image(&self) -> XCapResult { capture( CGRect::new( - &CGPoint::new(self.x as f64, self.y as f64), - &CGSize::new(self.width as f64, self.height as f64), + CGPoint::new(self.x as f64, self.y as f64), + CGSize::new(self.width as f64, self.height as f64), ), - kCGWindowListOptionIncludingWindow, + CGWindowListOption::OptionIncludingWindow, self.id, ) } diff --git a/src/macos/mod.rs b/src/macos/mod.rs index ff876af..6e5b2d1 100644 --- a/src/macos/mod.rs +++ b/src/macos/mod.rs @@ -1,4 +1,3 @@ -mod boxed; mod capture; pub mod impl_monitor; diff --git a/src/windows/boxed.rs b/src/windows/boxed.rs deleted file mode 100644 index b52bdec..0000000 --- a/src/windows/boxed.rs +++ /dev/null @@ -1,179 +0,0 @@ -use std::{ops::Deref, ptr}; -use windows::{ - core::PCWSTR, - Win32::{ - Foundation::{CloseHandle, FreeLibrary, GetLastError, HANDLE, HMODULE, HWND}, - Graphics::Gdi::{CreateDCW, DeleteDC, DeleteObject, GetWindowDC, ReleaseDC, HBITMAP, HDC}, - System::{ - LibraryLoader::LoadLibraryW, - Threading::{OpenProcess, PROCESS_ACCESS_RIGHTS}, - }, - }, -}; - -use crate::{XCapError, XCapResult}; - -#[derive(Debug)] -pub(super) struct BoxHDC { - hdc: HDC, - hwnd: Option, -} - -impl Deref for BoxHDC { - type Target = HDC; - fn deref(&self) -> &Self::Target { - &self.hdc - } -} - -impl Drop for BoxHDC { - fn drop(&mut self) { - // ReleaseDC 与 DeleteDC 的区别 - // https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-releasedc - unsafe { - if let Some(hwnd) = self.hwnd { - if ReleaseDC(hwnd, self.hdc) != 1 { - log::error!("ReleaseDC {:?} failed", self) - } - } else if !DeleteDC(self.hdc).as_bool() { - log::error!("DeleteDC {:?} failed", self) - } - }; - } -} - -impl BoxHDC { - pub fn new(hdc: HDC, hwnd: Option) -> Self { - BoxHDC { hdc, hwnd } - } -} - -impl From<&[u16; 32]> for BoxHDC { - fn from(sz_device: &[u16; 32]) -> Self { - let sz_device_ptr = sz_device.as_ptr(); - - let hdc = unsafe { - CreateDCW( - PCWSTR(sz_device_ptr), - PCWSTR(sz_device_ptr), - PCWSTR(ptr::null()), - None, - ) - }; - - BoxHDC::new(hdc, None) - } -} - -impl From for BoxHDC { - fn from(hwnd: HWND) -> Self { - // GetWindowDC vs GetDC, GetDC 不会绘制窗口边框 - // https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-getwindowdc - let hdc = unsafe { GetWindowDC(hwnd) }; - - BoxHDC::new(hdc, Some(hwnd)) - } -} - -#[derive(Debug)] -pub(super) struct BoxHBITMAP(HBITMAP); - -impl Deref for BoxHBITMAP { - type Target = HBITMAP; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Drop for BoxHBITMAP { - fn drop(&mut self) { - // https://learn.microsoft.com/zh-cn/windows/win32/api/wingdi/nf-wingdi-createcompatiblebitmap - unsafe { - if !DeleteObject(self.0).as_bool() { - log::error!("DeleteObject {:?} failed", self) - } - }; - } -} - -impl BoxHBITMAP { - pub fn new(h_bitmap: HBITMAP) -> Self { - BoxHBITMAP(h_bitmap) - } -} - -#[derive(Debug)] -pub(super) struct BoxProcessHandle(HANDLE); - -impl Deref for BoxProcessHandle { - type Target = HANDLE; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Drop for BoxProcessHandle { - fn drop(&mut self) { - unsafe { - CloseHandle(self.0).unwrap_or_else(|_| log::error!("CloseHandle {:?} failed", self)); - }; - } -} - -impl BoxProcessHandle { - pub fn open( - dw_desired_access: PROCESS_ACCESS_RIGHTS, - b_inherit_handle: bool, - dw_process_id: u32, - ) -> XCapResult { - unsafe { - let h_process = OpenProcess(dw_desired_access, b_inherit_handle, dw_process_id)?; - - if h_process.is_invalid() { - return Err(XCapError::new(format!( - "OpenProcess error {:?}", - GetLastError() - ))); - } - - Ok(BoxProcessHandle(h_process)) - } - } -} - -#[derive(Debug)] -pub(super) struct BoxHModule(HMODULE); - -impl Deref for BoxHModule { - type Target = HMODULE; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Drop for BoxHModule { - fn drop(&mut self) { - unsafe { - if let Err(err) = FreeLibrary(self.0) { - log::error!("FreeLibrary {:?} failed {:?}", self, err); - } - }; - } -} - -impl BoxHModule { - pub fn new(lib_filename: PCWSTR) -> XCapResult { - unsafe { - let hmodule = LoadLibraryW(lib_filename)?; - - if hmodule.is_invalid() { - return Err(XCapError::new(format!( - "LoadLibraryW error {:?}", - GetLastError() - ))); - } - - Ok(Self(hmodule)) - } - } -} diff --git a/src/windows/capture.rs b/src/windows/capture.rs index fa101b5..e010901 100644 --- a/src/windows/capture.rs +++ b/src/windows/capture.rs @@ -1,29 +1,28 @@ -use image::{DynamicImage, RgbaImage}; use std::{ffi::c_void, mem}; + +use image::{DynamicImage, RgbaImage}; +use scopeguard::guard; use windows::Win32::{ Foundation::HWND, Graphics::{ Dwm::DwmIsCompositionEnabled, Gdi::{ - BitBlt, CreateCompatibleBitmap, CreateCompatibleDC, GetCurrentObject, GetDIBits, - GetObjectW, SelectObject, BITMAP, BITMAPINFO, BITMAPINFOHEADER, DIB_RGB_COLORS, - OBJ_BITMAP, SRCCOPY, + BitBlt, CreateCompatibleBitmap, CreateCompatibleDC, DeleteDC, DeleteObject, + GetCurrentObject, GetDIBits, GetObjectW, GetWindowDC, ReleaseDC, SelectObject, BITMAP, + BITMAPINFO, BITMAPINFOHEADER, DIB_RGB_COLORS, HBITMAP, HDC, OBJ_BITMAP, SRCCOPY, }, }, - Storage::Xps::{PrintWindow, PRINT_WINDOW_FLAGS, PW_CLIENTONLY}, - UI::WindowsAndMessaging::{GetDesktopWindow, PW_RENDERFULLCONTENT, WINDOWINFO}, + Storage::Xps::{PrintWindow, PRINT_WINDOW_FLAGS}, + UI::WindowsAndMessaging::{GetDesktopWindow, WINDOWINFO}, }; use crate::error::{XCapError, XCapResult}; -use super::{ - boxed::{BoxHBITMAP, BoxHDC}, - utils::{bgra_to_rgba_image, get_os_major_version}, -}; +use super::utils::{bgra_to_rgba_image, get_os_major_version}; fn to_rgba_image( - box_hdc_mem: BoxHDC, - box_h_bitmap: BoxHBITMAP, + hdc_mem: HDC, + h_bitmap: HBITMAP, width: i32, height: i32, ) -> XCapResult { @@ -47,8 +46,8 @@ fn to_rgba_image( unsafe { // 读取数据到 buffer 中 let is_failed = GetDIBits( - *box_hdc_mem, - *box_h_bitmap, + hdc_mem, + h_bitmap, 0, height as u32, Some(buffer.as_mut_ptr().cast()), @@ -68,36 +67,51 @@ fn to_rgba_image( pub fn capture_monitor(x: i32, y: i32, width: i32, height: i32) -> XCapResult { unsafe { let hwnd = GetDesktopWindow(); - let box_hdc_desktop_window = BoxHDC::from(hwnd); + let scope_guard_hdc_desktop_window = guard(GetWindowDC(Some(hwnd)), |val| { + if ReleaseDC(Some(hwnd), val) != 1 { + log::error!("ReleaseDC {:?} failed", val) + } + }); // 内存中的HDC,使用 DeleteDC 函数释放 // https://learn.microsoft.com/zh-cn/windows/win32/api/wingdi/nf-wingdi-createcompatibledc - let box_hdc_mem = BoxHDC::new(CreateCompatibleDC(*box_hdc_desktop_window), None); - let box_h_bitmap = BoxHBITMAP::new(CreateCompatibleBitmap( - *box_hdc_desktop_window, - width, - height, - )); + let scope_guard_mem = guard( + CreateCompatibleDC(Some(*scope_guard_hdc_desktop_window)), + |val| { + if !DeleteDC(val).as_bool() { + log::error!("DeleteDC {:?} failed", val) + } + }, + ); + + let scope_guard_h_bitmap = guard( + CreateCompatibleBitmap(*scope_guard_hdc_desktop_window, width, height), + |val| { + if DeleteObject(val.into()).as_bool() { + log::error!("DeleteObject {:?} failed", val); + } + }, + ); // 使用SelectObject函数将这个位图选择到DC中 - SelectObject(*box_hdc_mem, *box_h_bitmap); + SelectObject(*scope_guard_mem, (*scope_guard_h_bitmap).into()); // 拷贝原始图像到内存 // 这里不需要缩放图片,所以直接使用BitBlt // 如需要缩放,则使用 StretchBlt BitBlt( - *box_hdc_mem, + *scope_guard_mem, 0, 0, width, height, - *box_hdc_desktop_window, + Some(*scope_guard_hdc_desktop_window), x, y, SRCCOPY, )?; - to_rgba_image(box_hdc_mem, box_h_bitmap, width, height) + to_rgba_image(*scope_guard_mem, *scope_guard_h_bitmap, width, height) } } @@ -108,13 +122,18 @@ pub fn capture_window( window_info: &WINDOWINFO, ) -> XCapResult { unsafe { - let box_hdc_window: BoxHDC = BoxHDC::from(hwnd); let rc_window = window_info.rcWindow; let mut width = rc_window.right - rc_window.left; let mut height = rc_window.bottom - rc_window.top; - let hgdi_obj = GetCurrentObject(*box_hdc_window, OBJ_BITMAP); + let scope_guard_hdc_window = guard(GetWindowDC(Some(hwnd)), |val| { + if ReleaseDC(Some(hwnd), val) != 1 { + log::error!("ReleaseDC {:?} failed", val) + } + }); + + let hgdi_obj = GetCurrentObject(*scope_guard_hdc_window, OBJ_BITMAP); let mut bitmap = BITMAP::default(); let mut horizontal_scale = 1.0; @@ -135,34 +154,45 @@ pub fn capture_window( // 内存中的HDC,使用 DeleteDC 函数释放 // https://learn.microsoft.com/zh-cn/windows/win32/api/wingdi/nf-wingdi-createcompatibledc - let box_hdc_mem = BoxHDC::new(CreateCompatibleDC(*box_hdc_window), None); - let box_h_bitmap = BoxHBITMAP::new(CreateCompatibleBitmap(*box_hdc_window, width, height)); - - let previous_object = SelectObject(*box_hdc_mem, *box_h_bitmap); + let scope_guard_hdc_mem = guard(CreateCompatibleDC(Some(*scope_guard_hdc_window)), |val| { + if !DeleteDC(val).as_bool() { + log::error!("DeleteDC {:?} failed", val) + } + }); + let scope_guard_h_bitmap = guard( + CreateCompatibleBitmap(*scope_guard_hdc_window, width, height), + |val| { + if DeleteObject(val.into()).as_bool() { + log::error!("DeleteObject {:?} failed", val); + } + }, + ); + + let previous_object = SelectObject(*scope_guard_hdc_mem, (*scope_guard_h_bitmap).into()); let mut is_success = false; // https://webrtc.googlesource.com/src.git/+/refs/heads/main/modules/desktop_capture/win/window_capturer_win_gdi.cc#301 if get_os_major_version() >= 8 { - is_success = PrintWindow(hwnd, *box_hdc_mem, PRINT_WINDOW_FLAGS(2)).as_bool(); + is_success = PrintWindow(hwnd, *scope_guard_hdc_mem, PRINT_WINDOW_FLAGS(2)).as_bool(); } if !is_success && DwmIsCompositionEnabled()?.as_bool() { - is_success = PrintWindow(hwnd, *box_hdc_mem, PRINT_WINDOW_FLAGS(0)).as_bool(); + is_success = PrintWindow(hwnd, *scope_guard_hdc_mem, PRINT_WINDOW_FLAGS(0)).as_bool(); } if !is_success { - is_success = PrintWindow(hwnd, *box_hdc_mem, PRINT_WINDOW_FLAGS(4)).as_bool(); + is_success = PrintWindow(hwnd, *scope_guard_hdc_mem, PRINT_WINDOW_FLAGS(4)).as_bool(); } if !is_success { is_success = BitBlt( - *box_hdc_mem, + *scope_guard_hdc_mem, 0, 0, width, height, - *box_hdc_window, + Some(*scope_guard_hdc_window), 0, 0, SRCCOPY, @@ -170,9 +200,9 @@ pub fn capture_window( .is_ok(); } - SelectObject(*box_hdc_mem, previous_object); + SelectObject(*scope_guard_hdc_mem, previous_object); - let image = to_rgba_image(box_hdc_mem, box_h_bitmap, width, height)?; + let image = to_rgba_image(*scope_guard_hdc_mem, *scope_guard_h_bitmap, width, height)?; let mut rc_client = window_info.rcClient; diff --git a/src/windows/impl_monitor.rs b/src/windows/impl_monitor.rs index 796dc90..eab78cf 100644 --- a/src/windows/impl_monitor.rs +++ b/src/windows/impl_monitor.rs @@ -1,15 +1,16 @@ -use std::mem; +use std::{mem, ptr}; use image::RgbaImage; +use scopeguard::guard; use windows::{ core::{s, w, HRESULT, PCWSTR}, Win32::{ 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, + CreateDCW, DeleteDC, 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, Threading::GetCurrentProcess}, UI::WindowsAndMessaging::MONITORINFOF_PRIMARY, @@ -19,10 +20,9 @@ use windows::{ use crate::error::{XCapError, XCapResult}; use super::{ - boxed::{BoxHDC, BoxHModule}, capture::capture_monitor, impl_video_recorder::ImplVideoRecorder, - utils::{get_process_is_dpi_awareness, wide_string_to_string}, + utils::{get_monitor_name, get_process_is_dpi_awareness, load_library}, }; // A 函数与 W 函数区别 @@ -31,7 +31,7 @@ use super::{ #[derive(Debug, Clone)] pub(crate) struct ImplMonitor { #[allow(unused)] - pub hmonitor: HMONITOR, + pub h_monitor: HMONITOR, #[allow(unused)] pub monitor_info_ex_w: MONITORINFOEXW, pub id: u32, @@ -47,14 +47,14 @@ pub(crate) struct ImplMonitor { } extern "system" fn monitor_enum_proc( - hmonitor: HMONITOR, + h_monitor: HMONITOR, _: HDC, _: *mut RECT, state: LPARAM, ) -> BOOL { unsafe { let state = Box::leak(Box::from_raw(state.0 as *mut Vec)); - state.push(hmonitor); + state.push(h_monitor); TRUE } @@ -76,13 +76,13 @@ fn get_dev_mode_w(monitor_info_exw: &MONITORINFOEXW) -> XCapResult { // 定义 GetDpiForMonitor 函数的类型 type GetDpiForMonitor = unsafe extern "system" fn( - hmonitor: HMONITOR, + h_monitor: HMONITOR, dpi_type: u32, dpi_x: *mut u32, dpi_y: *mut u32, ) -> HRESULT; -fn get_hi_dpi_scale_factor(hmonitor: HMONITOR) -> XCapResult { +fn get_hi_dpi_scale_factor(h_monitor: HMONITOR) -> XCapResult { unsafe { let current_process_is_dpi_awareness: bool = get_process_is_dpi_awareness(GetCurrentProcess())?; @@ -92,10 +92,11 @@ fn get_hi_dpi_scale_factor(hmonitor: HMONITOR) -> XCapResult { return Err(XCapError::new("Process not DPI aware")); } - let box_hmodule = BoxHModule::new(w!("Shcore.dll"))?; + let scope_guard_hmodule = load_library(w!("Shcore.dll"))?; - let get_dpi_for_monitor_proc_address = GetProcAddress(*box_hmodule, s!("GetDpiForMonitor")) - .ok_or(XCapError::new("GetProcAddress GetDpiForMonitor failed"))?; + let get_dpi_for_monitor_proc_address = + GetProcAddress(*scope_guard_hmodule, s!("GetDpiForMonitor")) + .ok_or(XCapError::new("GetProcAddress GetDpiForMonitor failed"))?; let get_dpi_for_monitor: GetDpiForMonitor = mem::transmute(get_dpi_for_monitor_proc_address); @@ -104,19 +105,33 @@ fn get_hi_dpi_scale_factor(hmonitor: HMONITOR) -> XCapResult { let mut dpi_y = 0; // https://learn.microsoft.com/zh-cn/windows/win32/api/shellscalingapi/ne-shellscalingapi-monitor_dpi_type - get_dpi_for_monitor(hmonitor, 0, &mut dpi_x, &mut dpi_y).ok()?; + get_dpi_for_monitor(h_monitor, 0, &mut dpi_x, &mut dpi_y).ok()?; Ok(dpi_x as f32 / 96.0) } } -fn get_scale_factor(hmonitor: HMONITOR, box_hdc_monitor: BoxHDC) -> XCapResult { - let scale_factor = get_hi_dpi_scale_factor(hmonitor).unwrap_or_else(|err| { +fn get_scale_factor(h_monitor: HMONITOR, monitor_info_ex_w: MONITORINFOEXW) -> XCapResult { + let scale_factor = get_hi_dpi_scale_factor(h_monitor).unwrap_or_else(|err| { log::info!("{}", err); // https://learn.microsoft.com/zh-cn/windows/win32/api/wingdi/nf-wingdi-getdevicecaps unsafe { - let physical_width = GetDeviceCaps(*box_hdc_monitor, DESKTOPHORZRES); - let logical_width = GetDeviceCaps(*box_hdc_monitor, HORZRES); + let scope_guard_hdc = guard( + CreateDCW( + PCWSTR(monitor_info_ex_w.szDevice.as_ptr()), + PCWSTR(monitor_info_ex_w.szDevice.as_ptr()), + PCWSTR(ptr::null()), + None, + ), + |val| { + if !DeleteDC(val).as_bool() { + log::error!("DeleteDC {:?} failed", val) + } + }, + ); + + let physical_width = GetDeviceCaps(Some(*scope_guard_hdc), DESKTOPHORZRES); + let logical_width = GetDeviceCaps(Some(*scope_guard_hdc), HORZRES); physical_width as f32 / logical_width as f32 } @@ -126,14 +141,17 @@ fn get_scale_factor(hmonitor: HMONITOR, box_hdc_monitor: BoxHDC) -> XCapResult XCapResult { + pub fn new(h_monitor: HMONITOR) -> XCapResult { let mut monitor_info_ex_w = MONITORINFOEXW::default(); monitor_info_ex_w.monitorInfo.cbSize = mem::size_of::() as u32; let monitor_info_ex_w_ptr = &mut monitor_info_ex_w as *mut MONITORINFOEXW as *mut MONITORINFO; // https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-getmonitorinfoa - unsafe { GetMonitorInfoW(hmonitor, monitor_info_ex_w_ptr).ok()? }; + unsafe { GetMonitorInfoW(h_monitor, monitor_info_ex_w_ptr).ok()? }; + + let name = get_monitor_name(monitor_info_ex_w) + .unwrap_or(format!("Unknown Monitor {}", h_monitor.0 as u32)); let dev_mode_w = get_dev_mode_w(&monitor_info_ex_w)?; @@ -151,14 +169,13 @@ impl ImplMonitor { _ => 0.0, }; - let box_hdc_monitor = BoxHDC::from(&monitor_info_ex_w.szDevice); - let scale_factor = get_scale_factor(hmonitor, box_hdc_monitor)?; + let scale_factor = get_scale_factor(h_monitor, monitor_info_ex_w)?; Ok(ImplMonitor { - hmonitor, + h_monitor, monitor_info_ex_w, - id: hmonitor.0 as u32, - name: wide_string_to_string(&monitor_info_ex_w.szDevice)?, + id: h_monitor.0 as u32, + name, x: dm_position.x, y: dm_position.y, width: dm_pels_width, @@ -173,9 +190,9 @@ impl ImplMonitor { pub fn all() -> XCapResult> { let hmonitors_mut_ptr: *mut Vec = Box::into_raw(Box::default()); - let hmonitors = unsafe { + let h_monitors = unsafe { EnumDisplayMonitors( - HDC::default(), + None, None, Some(monitor_enum_proc), LPARAM(hmonitors_mut_ptr as isize), @@ -184,13 +201,13 @@ impl ImplMonitor { Box::from_raw(hmonitors_mut_ptr) }; - let mut impl_monitors = Vec::with_capacity(hmonitors.len()); + let mut impl_monitors = Vec::with_capacity(h_monitors.len()); - for &hmonitor in hmonitors.iter() { - if let Ok(impl_monitor) = ImplMonitor::new(hmonitor) { + for &h_monitor in h_monitors.iter() { + if let Ok(impl_monitor) = ImplMonitor::new(h_monitor) { impl_monitors.push(impl_monitor); } else { - log::error!("ImplMonitor::new({:?}) failed", hmonitor); + log::error!("ImplMonitor::new({:?}) failed", h_monitor); } } @@ -199,13 +216,13 @@ impl ImplMonitor { pub fn from_point(x: i32, y: i32) -> XCapResult { let point = POINT { x, y }; - let hmonitor = unsafe { MonitorFromPoint(point, MONITOR_DEFAULTTONULL) }; + let h_monitor = unsafe { MonitorFromPoint(point, MONITOR_DEFAULTTONULL) }; - if hmonitor.is_invalid() { + if h_monitor.is_invalid() { return Err(XCapError::new("Not found monitor")); } - ImplMonitor::new(hmonitor) + ImplMonitor::new(h_monitor) } } @@ -215,6 +232,6 @@ impl ImplMonitor { } pub fn video_recorder(&self) -> XCapResult { - ImplVideoRecorder::new(self.hmonitor) + ImplVideoRecorder::new(self.h_monitor) } } diff --git a/src/windows/impl_video_recorder.rs b/src/windows/impl_video_recorder.rs index 727abb3..13973ad 100644 --- a/src/windows/impl_video_recorder.rs +++ b/src/windows/impl_video_recorder.rs @@ -2,19 +2,22 @@ use std::{slice, sync::Arc}; use windows::{ core::Interface, - Win32::Graphics::{ - Direct3D::D3D_DRIVER_TYPE_HARDWARE, - Direct3D11::{ - D3D11CreateDevice, ID3D11Device, ID3D11DeviceContext, ID3D11Resource, ID3D11Texture2D, - D3D11_CPU_ACCESS_READ, D3D11_CREATE_DEVICE_BGRA_SUPPORT, - D3D11_CREATE_DEVICE_SINGLETHREADED, D3D11_MAPPED_SUBRESOURCE, D3D11_MAP_READ, - D3D11_SDK_VERSION, D3D11_TEXTURE2D_DESC, D3D11_USAGE_STAGING, + Win32::{ + Foundation::HMODULE, + Graphics::{ + Direct3D::D3D_DRIVER_TYPE_HARDWARE, + Direct3D11::{ + D3D11CreateDevice, ID3D11Device, ID3D11DeviceContext, ID3D11Resource, + ID3D11Texture2D, D3D11_CPU_ACCESS_READ, D3D11_CREATE_DEVICE_BGRA_SUPPORT, + D3D11_CREATE_DEVICE_SINGLETHREADED, D3D11_MAPPED_SUBRESOURCE, D3D11_MAP_READ, + D3D11_SDK_VERSION, D3D11_TEXTURE2D_DESC, D3D11_USAGE_STAGING, + }, + Dxgi::{ + IDXGIDevice, IDXGIOutput1, IDXGIOutputDuplication, IDXGIResource, + DXGI_ERROR_WAIT_TIMEOUT, DXGI_OUTDUPL_FRAME_INFO, + }, + Gdi::HMONITOR, }, - Dxgi::{ - IDXGIDevice, IDXGIOutput1, IDXGIOutputDuplication, IDXGIResource, - DXGI_ERROR_WAIT_TIMEOUT, DXGI_OUTDUPL_FRAME_INFO, - }, - Gdi::HMONITOR, }, }; @@ -81,13 +84,13 @@ pub struct ImplVideoRecorder { } impl ImplVideoRecorder { - pub fn new(hmonitor: HMONITOR) -> XCapResult { + pub fn new(h_monitor: HMONITOR) -> XCapResult { unsafe { let mut d3d_device = None; D3D11CreateDevice( None, D3D_DRIVER_TYPE_HARDWARE, - None, + HMODULE::default(), D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_SINGLETHREADED, None, D3D11_SDK_VERSION, @@ -111,7 +114,7 @@ impl ImplVideoRecorder { let output1 = output.cast::()?; let duplication = output1.DuplicateOutput(&dxgi_device)?; - if output_desc.Monitor == hmonitor { + if output_desc.Monitor == h_monitor { return Ok(Self { d3d_device, d3d_context, diff --git a/src/windows/impl_window.rs b/src/windows/impl_window.rs index 8d15500..7008ffb 100644 --- a/src/windows/impl_window.rs +++ b/src/windows/impl_window.rs @@ -1,10 +1,12 @@ use core::slice; -use image::RgbaImage; use std::{cmp::Ordering, ffi::c_void, mem, ptr}; + +use image::RgbaImage; +use widestring::U16CString; use windows::{ core::{HSTRING, PCWSTR}, Win32::{ - Foundation::{BOOL, HWND, LPARAM, MAX_PATH, RECT, TRUE}, + Foundation::{BOOL, HANDLE, HWND, LPARAM, MAX_PATH, RECT, TRUE}, Graphics::{ Dwm::{DwmGetWindowAttribute, DWMWA_CLOAKED, DWMWA_EXTENDED_FRAME_BOUNDS}, Gdi::{IsRectEmpty, MonitorFromWindow, MONITOR_DEFAULTTONEAREST}, @@ -24,15 +26,12 @@ use windows::{ }, }; -use crate::{ - error::XCapResult, - platform::{boxed::BoxProcessHandle, utils::log_last_error}, -}; +use crate::{error::XCapResult, platform::utils::log_last_error}; use super::{ capture::capture_window, impl_monitor::ImplMonitor, - utils::{get_process_is_dpi_awareness, wide_string_to_string}, + utils::{get_process_is_dpi_awareness, open_process}, }; #[derive(Debug, Clone)] @@ -79,7 +78,7 @@ fn is_window_cloaked(hwnd: HWND) -> bool { fn is_valid_window(hwnd: HWND) -> bool { unsafe { // ignore invisible windows - if !IsWindow(hwnd).as_bool() || !IsWindowVisible(hwnd).as_bool() { + if !IsWindow(Some(hwnd)).as_bool() || !IsWindowVisible(hwnd).as_bool() { return false; } @@ -96,8 +95,9 @@ fn is_valid_window(hwnd: HWND) -> bool { return false; } - let class_name = - wide_string_to_string(&lp_class_name[0..lp_class_name_length]).unwrap_or_default(); + let class_name = U16CString::from_vec_truncate(&lp_class_name[0..lp_class_name_length]) + .to_string() + .unwrap_or_default(); if class_name.is_empty() { return false; } @@ -183,7 +183,9 @@ fn get_window_title(hwnd: HWND) -> XCapResult { let text_length = GetWindowTextLengthW(hwnd); let mut wide_buffer = vec![0u16; (text_length + 1) as usize]; GetWindowTextW(hwnd, &mut wide_buffer); - wide_string_to_string(&wide_buffer) + let window_title = U16CString::from_vec_truncate(wide_buffer).to_string()?; + + Ok(window_title) } } @@ -193,19 +195,21 @@ struct LangCodePage { pub w_code_page: u16, } -fn get_module_basename(box_process_handle: BoxProcessHandle) -> XCapResult { +fn get_module_basename(handle: HANDLE) -> XCapResult { unsafe { // 默认使用 module_basename let mut module_base_name_w = [0; MAX_PATH as usize]; - let result = GetModuleBaseNameW(*box_process_handle, None, &mut module_base_name_w); + let result = GetModuleBaseNameW(handle, None, &mut module_base_name_w); if result == 0 { log_last_error("GetModuleBaseNameW"); - GetModuleFileNameExW(*box_process_handle, None, &mut module_base_name_w); + GetModuleFileNameExW(Some(handle), None, &mut module_base_name_w); } - wide_string_to_string(&module_base_name_w) + let module_basename = U16CString::from_vec_truncate(module_base_name_w).to_string()?; + + Ok(module_basename) } } @@ -219,17 +223,16 @@ fn get_window_pid(hwnd: HWND) -> u32 { fn get_app_name(pid: u32) -> XCapResult { unsafe { - let box_process_handle = - match BoxProcessHandle::open(PROCESS_QUERY_LIMITED_INFORMATION, false, pid) { - Ok(box_handle) => box_handle, - Err(err) => { - log::error!("{}", err); - return Ok(String::new()); - } - }; + let scope_guard_handle = match open_process(PROCESS_QUERY_LIMITED_INFORMATION, false, pid) { + Ok(box_handle) => box_handle, + Err(err) => { + log::error!("{}", err); + return Ok(String::new()); + } + }; let mut filename = [0; MAX_PATH as usize]; - GetModuleFileNameExW(*box_process_handle, None, &mut filename); + GetModuleFileNameExW(Some(*scope_guard_handle), None, &mut filename); let pcw_filename = PCWSTR::from_raw(filename.as_ptr()); @@ -237,14 +240,14 @@ fn get_app_name(pid: u32) -> XCapResult { if file_version_info_size_w == 0 { log_last_error("GetFileVersionInfoSizeW"); - return get_module_basename(box_process_handle); + return get_module_basename(*scope_guard_handle); } let mut file_version_info = vec![0u16; file_version_info_size_w as usize]; GetFileVersionInfoW( pcw_filename, - 0, + None, file_version_info_size_w, file_version_info.as_mut_ptr().cast(), )?; @@ -296,7 +299,7 @@ fn get_app_name(pid: u32) -> XCapResult { } let value = slice::from_raw_parts(value_ptr.cast(), value_length as usize); - let attr = wide_string_to_string(value)?; + let attr = U16CString::from_vec_truncate(value).to_string()?; let attr = attr.trim(); if !attr.is_empty() { @@ -305,7 +308,7 @@ fn get_app_name(pid: u32) -> XCapResult { } } - get_module_basename(box_process_handle) + get_module_basename(*scope_guard_handle) } } @@ -323,7 +326,7 @@ impl ImplWindow { let pid = get_window_pid(hwnd); let app_name = get_app_name(pid)?; - let hmonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + let h_monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); let rc_client = window_info.rcClient; let is_minimized = IsIconic(hwnd).as_bool(); let is_maximized = IsZoomed(hwnd).as_bool(); @@ -336,7 +339,7 @@ impl ImplWindow { title, app_name, pid, - current_monitor: ImplMonitor::new(hmonitor)?, + current_monitor: ImplMonitor::new(h_monitor)?, x: rc_client.left, y: rc_client.top, z, @@ -380,9 +383,8 @@ impl ImplWindow { pub fn capture_image(&self) -> XCapResult { // 在win10之后,不同窗口有不同的dpi,所以可能存在截图不全或者截图有较大空白,实际窗口没有填充满图片 // 如果窗口不感知dpi,那么就不需要缩放,如果当前进程感知dpi,那么也不需要缩放 - let box_process_handle = - BoxProcessHandle::open(PROCESS_QUERY_LIMITED_INFORMATION, false, self.pid)?; - let window_is_dpi_awareness = get_process_is_dpi_awareness(*box_process_handle)?; + let scope_guard_handle = open_process(PROCESS_QUERY_LIMITED_INFORMATION, false, self.pid)?; + let window_is_dpi_awareness = get_process_is_dpi_awareness(*scope_guard_handle)?; let current_process_is_dpi_awareness = unsafe { get_process_is_dpi_awareness(GetCurrentProcess())? }; diff --git a/src/windows/mod.rs b/src/windows/mod.rs index 992b377..36192a5 100644 --- a/src/windows/mod.rs +++ b/src/windows/mod.rs @@ -1,4 +1,3 @@ -mod boxed; mod capture; mod utils; diff --git a/src/windows/utils.rs b/src/windows/utils.rs index 8ad77e0..03bf3df 100644 --- a/src/windows/utils.rs +++ b/src/windows/utils.rs @@ -1,31 +1,30 @@ use std::mem; use image::RgbaImage; +use scopeguard::{guard, ScopeGuard}; +use widestring::U16CString; use windows::{ - core::{s, w, HRESULT}, + core::{s, w, HRESULT, PCWSTR}, Win32::{ - Foundation::{GetLastError, HANDLE}, + Devices::Display::{ + DisplayConfigGetDeviceInfo, GetDisplayConfigBufferSizes, QueryDisplayConfig, + 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, + QDC_ONLY_ACTIVE_PATHS, + }, + Foundation::{CloseHandle, FreeLibrary, GetLastError, HANDLE, HMODULE}, + Graphics::Gdi::MONITORINFOEXW, System::{ - LibraryLoader::GetProcAddress, + LibraryLoader::{GetProcAddress, LoadLibraryW}, Registry::{RegGetValueW, HKEY_LOCAL_MACHINE, RRF_RT_REG_SZ}, + Threading::{OpenProcess, PROCESS_ACCESS_RIGHTS}, }, }, }; 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])? - } else { - String::from_utf16(wide_string)? - }; - - Ok(string) -} - pub(super) fn get_build_number() -> u32 { unsafe { let mut buf_len: u32 = 2048; @@ -47,7 +46,9 @@ pub(super) fn get_build_number() -> u32 { buf.set_len(buf_len as usize); - let build_version = wide_string_to_string(&buf).unwrap_or_default(); + let build_version = U16CString::from_vec_truncate(buf) + .to_string() + .unwrap_or_default(); build_version.parse().unwrap_or(0) } @@ -102,12 +103,12 @@ type GetProcessDpiAwareness = pub(super) fn get_process_is_dpi_awareness(process: HANDLE) -> XCapResult { unsafe { - let box_hmodule = BoxHModule::new(w!("Shcore.dll"))?; + let scope_guard_hmodule = load_library(w!("Shcore.dll"))?; let get_process_dpi_awareness_proc_address = - GetProcAddress(*box_hmodule, s!("GetProcessDpiAwareness")).ok_or(XCapError::new( - "GetProcAddress GetProcessDpiAwareness failed", - ))?; + 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); @@ -120,3 +121,122 @@ pub(super) fn get_process_is_dpi_awareness(process: HANDLE) -> XCapResult Ok(process_dpi_awareness != 0) } } + +pub(super) fn load_library( + lib_filename: PCWSTR, +) -> XCapResult> { + 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 {:?} failed {:?}", val, 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> { + 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 {:?} failed {:?}", val, err); + } + }); + + Ok(scope_guard_handle) + } +} + +pub(super) fn get_monitor_name(monitor_info_ex_w: MONITORINFOEXW) -> XCapResult { + 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::() 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::() as u32, + adapterId: path.sourceInfo.adapterId, + id: path.targetInfo.id, + }, + ..DISPLAYCONFIG_TARGET_DEVICE_NAME::default() + }; + + if DisplayConfigGetDeviceInfo(&mut target.header) != 0 { + continue; + } + + let name = + U16CString::from_vec_truncate(target.monitorFriendlyDeviceName).to_string()?; + + if name.is_empty() { + return Err(XCapError::new("Monitor name is empty")); + } + + return Ok(name); + } + + Err(XCapError::new("Get monitor name failed")) + } +}