From ab1906bf994cdf1f0769539eda71f0513921ed93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E7=B1=B3=E5=89=8D=E6=9C=89=E8=95=89=E7=9A=AE?= Date: Sat, 14 Dec 2024 13:31:19 +0800 Subject: [PATCH 01/11] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8window=E6=B3=A8?= =?UTF-8?q?=E5=86=8C=E8=A1=A8=E6=9B=BF=E6=8D=A2sysinfo=20(#169)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 2 +- src/windows/utils.rs | 27 ++++++++++++++++++++------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f0514d1..df28dee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,6 @@ vendored = ["dbus/vendored"] [dependencies] image = "0.25" log = "0.4" -sysinfo = "0.33" thiserror = "2.0" [target.'cfg(target_os = "macos")'.dependencies] @@ -33,6 +32,7 @@ windows = { version = "0.58", features = [ "Win32_Storage_Xps", "Win32_System_Threading", "Win32_System_ProcessStatus", + "Win32_System_Registry", "Win32_Storage_FileSystem", "Win32_Graphics_Dxgi", "Win32_Graphics_Direct3D", diff --git a/src/windows/utils.rs b/src/windows/utils.rs index ad968cd..df55587 100644 --- a/src/windows/utils.rs +++ b/src/windows/utils.rs @@ -1,5 +1,6 @@ use image::RgbaImage; -use sysinfo::System; +use windows::core::w; +use windows::Win32::System::Registry::{RegGetValueW, HKEY_LOCAL_MACHINE, RRF_RT_REG_DWORD}; use windows::Win32::Foundation::GetLastError; use crate::{error::XCapResult, XCapError}; @@ -15,12 +16,24 @@ pub(super) fn wide_string_to_string(wide_string: &[u16]) -> XCapResult { } pub(super) fn get_os_major_version() -> u8 { - System::os_version() - .map(|os_version| { - let strs: Vec<&str> = os_version.split(' ').collect(); - strs[0].parse::().unwrap_or(0) - }) - .unwrap_or(0) + unsafe { + let mut buf_len: u32 = 4; + let mut buf = [0u8; 4]; + let err = RegGetValueW( + HKEY_LOCAL_MACHINE, + w!(r"SOFTWARE\Microsoft\Windows NT\CurrentVersion"), + w!("CurrentMajorVersionNumber"), + RRF_RT_REG_DWORD, + None, + Some(buf.as_mut_ptr().cast()), + Some(&mut buf_len), + ); + if err.is_ok() { + u32::from_le_bytes(buf) as u8 + } else { + 0 + } + } } pub(super) fn log_last_error(label: T) { From 5ab3195a74c9abb7338b6028f7529019600b7b01 Mon Sep 17 00:00:00 2001 From: nashaofu <19303058+nashaofu@users.noreply.github.com> Date: Sat, 14 Dec 2024 16:22:59 +0800 Subject: [PATCH 02/11] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84windows?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E8=AF=BB=E5=8F=96=E6=96=B9=E5=BC=8F=20(#174)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 重构windows版本读取方式 * chore: 修改devcontainer --------- Co-authored-by: nashaofu --- .devcontainer/devcontainer.json | 7 +++-- src/windows/utils.rs | 48 ++++++++++++++++++++++++--------- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a9149d4..ceb8e0b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -23,6 +23,9 @@ // } // ] + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + // Use 'forwardPorts' to make a list of ports inside the container available locally. "forwardPorts": [6080, 5901], @@ -33,9 +36,9 @@ "customizations": { "vscode": { "extensions": [ - "wengerk.highlight-bad-chars", "streetsidesoftware.code-spell-checker", - "EditorConfig.EditorConfig", + "wengerk.highlight-bad-chars", + "editorconfig.editorconfig", "tamasfe.even-better-toml", "rust-lang.rust-analyzer" ] diff --git a/src/windows/utils.rs b/src/windows/utils.rs index df55587..07e780e 100644 --- a/src/windows/utils.rs +++ b/src/windows/utils.rs @@ -1,7 +1,11 @@ use image::RgbaImage; -use windows::core::w; -use windows::Win32::System::Registry::{RegGetValueW, HKEY_LOCAL_MACHINE, RRF_RT_REG_DWORD}; -use windows::Win32::Foundation::GetLastError; +use windows::{ + core::w, + Win32::{ + Foundation::GetLastError, + System::Registry::{RegGetValueW, HKEY_LOCAL_MACHINE, RRF_RT_REG_SZ}, + }, +}; use crate::{error::XCapResult, XCapError}; @@ -15,24 +19,44 @@ pub(super) fn wide_string_to_string(wide_string: &[u16]) -> XCapResult { Ok(string) } -pub(super) fn get_os_major_version() -> u8 { +pub(super) fn get_build_number() -> u32 { unsafe { - let mut buf_len: u32 = 4; - let mut buf = [0u8; 4]; + let mut buf_len: u32 = 2048; + let mut buf: Vec = Vec::with_capacity(buf_len as usize); + let err = RegGetValueW( HKEY_LOCAL_MACHINE, w!(r"SOFTWARE\Microsoft\Windows NT\CurrentVersion"), - w!("CurrentMajorVersionNumber"), - RRF_RT_REG_DWORD, + w!("CurrentBuildNumber"), + RRF_RT_REG_SZ, None, Some(buf.as_mut_ptr().cast()), Some(&mut buf_len), ); - if err.is_ok() { - u32::from_le_bytes(buf) as u8 - } else { - 0 + + if err.is_err() { + return 0; } + + buf.set_len(buf_len as usize); + + let build_version = wide_string_to_string(&buf).unwrap_or_default(); + + build_version.parse().unwrap_or(0) + } +} + +pub(super) fn get_os_major_version() -> u8 { + let build_number = get_build_number(); + // https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions + if build_number >= 22000 { + 11 + } else if build_number >= 10240 { + 10 + } else if build_number >= 9200 { + 8 + } else { + 7 } } From 574f9f8db29e969095018ec7e949c68d7e732bcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E7=B1=B3=E5=89=8D=E6=9C=89=E8=95=89=E7=9A=AE?= Date: Sat, 14 Dec 2024 16:33:49 +0800 Subject: [PATCH 03/11] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0image=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E4=BE=9D=E8=B5=96=E4=B8=BA=E5=8F=AF=E9=80=89features?= =?UTF-8?q?=20(#170)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 添加image默认依赖为可选features * 默认开启default-image --- Cargo.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index df28dee..30fe2ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,10 +12,11 @@ keywords = ["screen", "monitor", "window", "capture", "image"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] +default = ["default-image"] vendored = ["dbus/vendored"] - +default-image = ["image/default"] [dependencies] -image = "0.25" +image = { version = "0.25", default-features = false, features = ["png"] } log = "0.4" thiserror = "2.0" From ce2fe4f6f198f0dd3020a396ff14feb885c1e69d Mon Sep 17 00:00:00 2001 From: nashaofu <19303058+nashaofu@users.noreply.github.com> Date: Sat, 21 Dec 2024 22:53:29 +0800 Subject: [PATCH 04/11] =?UTF-8?q?feat:=20windows=E6=94=AF=E6=8C=81hidpi=20?= =?UTF-8?q?(#177)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 自动感知dpi * chore: 修改版本 --------- Co-authored-by: nashaofu --- Cargo.toml | 7 ++-- src/windows/boxed.rs | 44 ++++++++++++++++++- src/windows/impl_monitor.rs | 84 ++++++++++++++++++++++++++++++++----- 3 files changed, 119 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 30fe2ea..ec672b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "xcap" -version = "0.1.0" +version = "0.1.1" 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" @@ -12,9 +12,9 @@ keywords = ["screen", "monitor", "window", "capture", "image"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -default = ["default-image"] vendored = ["dbus/vendored"] -default-image = ["image/default"] +image = ["image/default"] + [dependencies] image = { version = "0.25", default-features = false, features = ["png"] } log = "0.4" @@ -29,6 +29,7 @@ windows = { version = "0.58", features = [ "Win32_Foundation", "Win32_Graphics_Gdi", "Win32_Graphics_Dwm", + "Win32_System_LibraryLoader", "Win32_UI_WindowsAndMessaging", "Win32_Storage_Xps", "Win32_System_Threading", diff --git a/src/windows/boxed.rs b/src/windows/boxed.rs index e579bda..b52bdec 100644 --- a/src/windows/boxed.rs +++ b/src/windows/boxed.rs @@ -2,9 +2,12 @@ use std::{ops::Deref, ptr}; use windows::{ core::PCWSTR, Win32::{ - Foundation::{CloseHandle, GetLastError, HANDLE, HWND}, + Foundation::{CloseHandle, FreeLibrary, GetLastError, HANDLE, HMODULE, HWND}, Graphics::Gdi::{CreateDCW, DeleteDC, DeleteObject, GetWindowDC, ReleaseDC, HBITMAP, HDC}, - System::Threading::{OpenProcess, PROCESS_ACCESS_RIGHTS}, + System::{ + LibraryLoader::LoadLibraryW, + Threading::{OpenProcess, PROCESS_ACCESS_RIGHTS}, + }, }, }; @@ -137,3 +140,40 @@ impl BoxProcessHandle { } } } + +#[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/impl_monitor.rs b/src/windows/impl_monitor.rs index cd7ae70..d83da50 100644 --- a/src/windows/impl_monitor.rs +++ b/src/windows/impl_monitor.rs @@ -1,15 +1,17 @@ -use image::RgbaImage; use std::mem; + +use image::RgbaImage; use windows::{ - core::PCWSTR, + core::{s, w, HRESULT, PCWSTR}, Win32::{ - Foundation::{BOOL, LPARAM, POINT, RECT, TRUE}, + Foundation::{BOOL, HANDLE, 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, UI::WindowsAndMessaging::MONITORINFOF_PRIMARY, }, }; @@ -17,7 +19,9 @@ use windows::{ use crate::error::{XCapError, XCapResult}; use super::{ - boxed::BoxHDC, capture::capture_monitor, impl_video_recorder::ImplVideoRecorder, + boxed::{BoxHDC, BoxHModule}, + capture::capture_monitor, + impl_video_recorder::ImplVideoRecorder, utils::wide_string_to_string, }; @@ -70,6 +74,70 @@ 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, + dpi_type: u32, + dpi_x: *mut u32, + dpi_y: *mut u32, +) -> HRESULT; + +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()?; + + // 当前进程不感知 DPI,则回退到 GetDeviceCaps 获取 DPI + if process_dpi_awareness == 0 { + return Err(XCapError::new("Process not DPI aware")); + } + + let get_dpi_for_monitor_proc_address = GetProcAddress(*box_hmodule, s!("GetDpiForMonitor")) + .ok_or(XCapError::new("GetProcAddress GetDpiForMonitor failed"))?; + + let get_dpi_for_monitor: GetDpiForMonitor = + mem::transmute(get_dpi_for_monitor_proc_address); + + let mut dpi_x = 0; + 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()?; + + 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| { + 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); + + physical_width as f32 / logical_width as f32 + } + }); + + Ok(scale_factor) +} + impl ImplMonitor { pub fn new(hmonitor: HMONITOR) -> XCapResult { let mut monitor_info_ex_w = MONITORINFOEXW::default(); @@ -97,13 +165,7 @@ impl ImplMonitor { }; let box_hdc_monitor = BoxHDC::from(&monitor_info_ex_w.szDevice); - - let scale_factor = unsafe { - let physical_width = GetDeviceCaps(*box_hdc_monitor, DESKTOPHORZRES); - let logical_width = GetDeviceCaps(*box_hdc_monitor, HORZRES); - - physical_width as f32 / logical_width as f32 - }; + let scale_factor = get_scale_factor(hmonitor, box_hdc_monitor)?; Ok(ImplMonitor { hmonitor, From 5457eb2ed65dce867e246198d6ac514c03dbe7dd Mon Sep 17 00:00:00 2001 From: nashaofu <19303058+nashaofu@users.noreply.github.com> Date: Sun, 22 Dec 2024 10:17:16 +0800 Subject: [PATCH 05/11] feat: list window in z order (#173) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: window focus * refactor: window is focused is mouse in it * fix: fix windows build error * fix: formatted code * feat: 修改 image 不作为默认feature * feat: macos 实现获取窗口是否聚焦 * feat(breaking): List windows in z order (#178) * feat: 窗口按照z轴顺序返回 * refactor(BREAKING CHANGE): remove window.refresh() --------- Co-authored-by: nashaofu * chore: fmt * feat: 修改 macos 窗口 z 值 (#179) Co-authored-by: nashaofu --------- Co-authored-by: Louis Beaumont Co-authored-by: nashaofu --- Cargo.toml | 2 +- examples/monitor_record.rs | 63 ++++++++-------------- examples/window.rs | 15 +++--- examples/window_record.rs | 70 ------------------------ examples/windows_monitor_record.rs | 30 ----------- src/linux/impl_window.rs | 62 ++++++++++++--------- src/macos/impl_window.rs | 87 ++++++------------------------ src/window.rs | 14 ++--- src/windows/impl_window.rs | 74 +++++++++++-------------- 9 files changed, 123 insertions(+), 294 deletions(-) delete mode 100644 examples/window_record.rs delete mode 100644 examples/windows_monitor_record.rs diff --git a/Cargo.toml b/Cargo.toml index ec672b2..bc088b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,9 +43,9 @@ windows = { version = "0.58", features = [ ] } [target.'cfg(target_os="linux")'.dependencies] +dbus = "0.9" percent-encoding = "2.3" xcb = { version = "1.5", features = ["randr"] } -dbus = { version = "0.9" } [dev-dependencies] fs_extra = "1.3" diff --git a/examples/monitor_record.rs b/examples/monitor_record.rs index 46b0b78..c5a24b5 100644 --- a/examples/monitor_record.rs +++ b/examples/monitor_record.rs @@ -1,47 +1,30 @@ -use fs_extra::dir; -use std::{ - thread, - time::{Duration, Instant}, -}; +use std::{sync::Arc, thread, time::Duration}; use xcap::Monitor; fn main() { - let monitors = Monitor::all().unwrap(); + let monitor = Monitor::from_point(100, 100).unwrap(); - dir::create_all("target/monitors", true).unwrap(); + let video_recorder = Arc::new(monitor.video_recorder().unwrap()); - let monitor = monitors.get(0).unwrap().clone(); - - let mut i = 0; - let frame = 20; - let start = Instant::now(); - let fps = 1000 / frame; - - loop { - i += 1; - let time = Instant::now(); - let image = monitor.capture_image().unwrap(); - image - .save(format!("target/monitors/monitor-{}.png", i,)) + let video_recorder_clone = video_recorder.clone(); + thread::spawn(move || { + video_recorder_clone + .on_frame(|frame| { + println!("frame: {:?}", frame.width); + Ok(()) + }) .unwrap(); - let sleep_time = fps * i - start.elapsed().as_millis() as i128; - println!( - "sleep_time: {:?} current_step_time: {:?}", - sleep_time, - time.elapsed() - ); - if sleep_time > 0 { - thread::sleep(Duration::from_millis(sleep_time as u64)); - } - - if i >= 900 { - break; - } - } - - println!("time {:?}", start.elapsed()); - let actual_fps = 900 / start.elapsed().as_secs(); - println!("actual fps: {}", actual_fps); - - // ffmpeg -framerate {actual_fps} -i monitor-%d.png -c:v libx264 -pix_fmt yuv420p output.mp4 + }); + + println!("start"); + video_recorder.start().unwrap(); + thread::sleep(Duration::from_secs(2)); + println!("stop"); + video_recorder.stop().unwrap(); + thread::sleep(Duration::from_secs(2)); + println!("start"); + video_recorder.start().unwrap(); + thread::sleep(Duration::from_secs(2)); + println!("stop"); + video_recorder.stop().unwrap(); } diff --git a/examples/window.rs b/examples/window.rs index 2cccebb..6fc67d9 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -1,23 +1,22 @@ -use std::time::Instant; +use std::thread; use xcap::Window; fn main() { - let start = Instant::now(); + thread::sleep(std::time::Duration::from_secs(3)); + let windows = Window::all().unwrap(); - println!("Window::all() 运行耗时: {:?}", start.elapsed()); - for window in windows { + for window in windows.clone() { println!( - "Window:\n id: {}\n title: {}\n app_name: {}\n monitor: {:?}\n position: {:?}\n size {:?}\n state {:?}\n", + "Window:\n id: {}\n title: {}\n app_name: {}\n pid: {}\n monitor: {:?}\n position: {:?}\n size {:?}\n state {:?}\n", window.id(), window.title(), window.app_name(), + window.pid(), window.current_monitor().name(), - (window.x(), window.y()), + (window.x(), window.y(), window.z()), (window.width(), window.height()), (window.is_minimized(), window.is_maximized()) ); } - - println!("运行耗时: {:?}", start.elapsed()); } diff --git a/examples/window_record.rs b/examples/window_record.rs deleted file mode 100644 index 9326fe5..0000000 --- a/examples/window_record.rs +++ /dev/null @@ -1,70 +0,0 @@ -use fs_extra::dir; -use std::{ - thread, - time::{Duration, Instant}, -}; -use xcap::Window; - -fn main() { - let windows = Window::all().unwrap(); - - dir::create_all("target/windows", true).unwrap(); - - let mut i = 0; - for window in &windows { - // 最小化的窗口不能截屏 - if window.is_minimized() { - continue; - } - - if window.title().contains("Chrome") { - break; - } - - println!( - "Window: {:?} {:?} {:?}", - window.title(), - (window.x(), window.y(), window.width(), window.height()), - (window.is_minimized(), window.is_maximized()) - ); - - i += 1; - } - - let mut win = windows.get(i).unwrap().clone(); - println!("{:?}", win); - - let mut i = 0; - let frame = 20; - let start = Instant::now(); - let fps = 1000 / frame; - - loop { - i += 1; - let time = Instant::now(); - win.refresh().unwrap(); - let image = win.capture_image().unwrap(); - image - .save(format!("target/windows/window-{}.png", i,)) - .unwrap(); - let sleep_time = fps * i - start.elapsed().as_millis() as i128; - println!( - "sleep_time: {:?} current_step_time: {:?}", - sleep_time, - time.elapsed() - ); - if sleep_time > 0 { - thread::sleep(Duration::from_millis(sleep_time as u64)); - } - - if i >= 900 { - break; - } - } - - println!("time {:?}", start.elapsed()); - let actual_fps = 900 / start.elapsed().as_secs(); - println!("actual fps: {}", actual_fps); - - // ffmpeg -framerate {actual_fps} -i window-%d.png -c:v libx264 -pix_fmt yuv420p output.mp4 -} diff --git a/examples/windows_monitor_record.rs b/examples/windows_monitor_record.rs deleted file mode 100644 index c5a24b5..0000000 --- a/examples/windows_monitor_record.rs +++ /dev/null @@ -1,30 +0,0 @@ -use std::{sync::Arc, thread, time::Duration}; -use xcap::Monitor; - -fn main() { - let monitor = Monitor::from_point(100, 100).unwrap(); - - let video_recorder = Arc::new(monitor.video_recorder().unwrap()); - - let video_recorder_clone = video_recorder.clone(); - thread::spawn(move || { - video_recorder_clone - .on_frame(|frame| { - println!("frame: {:?}", frame.width); - Ok(()) - }) - .unwrap(); - }); - - println!("start"); - video_recorder.start().unwrap(); - thread::sleep(Duration::from_secs(2)); - println!("stop"); - video_recorder.stop().unwrap(); - thread::sleep(Duration::from_secs(2)); - println!("start"); - video_recorder.start().unwrap(); - thread::sleep(Duration::from_secs(2)); - println!("stop"); - video_recorder.stop().unwrap(); -} diff --git a/src/linux/impl_window.rs b/src/linux/impl_window.rs index 346a1da..3200b92 100644 --- a/src/linux/impl_window.rs +++ b/src/linux/impl_window.rs @@ -3,8 +3,8 @@ use std::str; use xcb::{ x::{ Atom, Drawable, GetGeometry, GetProperty, GetPropertyReply, InternAtom, QueryPointer, - TranslateCoordinates, Window, ATOM_ATOM, ATOM_NONE, ATOM_STRING, ATOM_WM_CLASS, - ATOM_WM_NAME, + TranslateCoordinates, Window, ATOM_ATOM, ATOM_CARDINAL, ATOM_NONE, ATOM_STRING, + ATOM_WM_CLASS, ATOM_WM_NAME, }, Connection, Xid, }; @@ -19,9 +19,11 @@ pub(crate) struct ImplWindow { pub id: u32, pub title: String, pub app_name: String, + pub pid: u32, pub current_monitor: ImplMonitor, pub x: i32, pub y: i32, + pub z: i32, pub width: u32, pub height: u32, pub is_minimized: bool, @@ -65,10 +67,24 @@ fn get_window_property( Ok(window_property_reply) } +pub fn get_window_pid(conn: &Connection, window: &Window) -> XCapResult { + let wm_pid_atom = get_atom(conn, "_NET_WM_PID")?; + + let reply = get_window_property(conn, *window, wm_pid_atom, ATOM_CARDINAL, 0, 4)?; + let value = reply.value::(); + + value + .first() + .ok_or(XCapError::new("Get window pid failed")) + .copied() +} + impl ImplWindow { fn new( conn: &Connection, window: &Window, + pid: u32, + z: i32, impl_monitors: &Vec, ) -> XCapResult { let title = { @@ -172,9 +188,11 @@ impl ImplWindow { id: window.resource_id(), title, app_name, + pid, current_monitor, x, y, + z, width, height, is_minimized, @@ -187,11 +205,14 @@ impl ImplWindow { let setup = conn.get_setup(); // https://github.com/rust-x-bindings/rust-xcb/blob/main/examples/get_all_windows.rs - let client_list_atom = get_atom(&conn, "_NET_CLIENT_LIST")?; + // https://specifications.freedesktop.org/wm-spec/1.5/ar01s03.html#id-1.4.4 + // list all windows by stacking order + let client_list_atom = get_atom(&conn, "_NET_CLIENT_LIST_STACKING")?; let mut impl_windows = Vec::new(); let impl_monitors = ImplMonitor::all()?; + let mut z = -1; for screen in setup.roots() { let root_window = screen.root(); @@ -210,14 +231,24 @@ impl ImplWindow { client_list_atom, ATOM_NONE, 0, - 100, + 1024, ) { Ok(list_window_reply) => list_window_reply, _ => continue, }; for client in list_window_reply.value::() { - if let Ok(impl_window) = ImplWindow::new(&conn, client, &impl_monitors) { + z += 1; + let pid = match get_window_pid(&conn, client) { + Ok(pid) => pid, + err => { + log::error!("{:?}", err); + continue; + } + }; + + if let Ok(impl_window) = ImplWindow::new(&conn, client, pid, z, &impl_monitors) + { impl_windows.push(impl_window); } else { log::error!( @@ -230,30 +261,13 @@ impl ImplWindow { } } + impl_windows.reverse(); + Ok(impl_windows) } } impl ImplWindow { - pub fn refresh(&mut self) -> XCapResult<()> { - let (conn, _) = Connection::connect(None)?; - let impl_monitors = ImplMonitor::all()?; - let impl_window = ImplWindow::new(&conn, &self.window, &impl_monitors)?; - - self.window = impl_window.window; - self.id = impl_window.id; - self.title = impl_window.title; - self.app_name = impl_window.app_name; - self.current_monitor = impl_window.current_monitor; - self.x = impl_window.x; - self.y = impl_window.y; - self.width = impl_window.width; - self.height = impl_window.height; - self.is_minimized = impl_window.is_minimized; - self.is_maximized = impl_window.is_maximized; - - Ok(()) - } pub fn capture_image(&self) -> XCapResult { capture_window(self) } diff --git a/src/macos/impl_window.rs b/src/macos/impl_window.rs index 8365f80..85d9ce2 100644 --- a/src/macos/impl_window.rs +++ b/src/macos/impl_window.rs @@ -1,3 +1,5 @@ +use std::ffi::c_void; + use core_foundation::{ array::{CFArrayGetCount, CFArrayGetValueAtIndex}, base::{FromVoid, TCFType}, @@ -14,7 +16,6 @@ use core_graphics::{ window::{kCGNullWindowID, kCGWindowSharingNone}, }; use image::RgbaImage; -use std::ffi::c_void; use crate::{error::XCapResult, XCapError}; @@ -25,9 +26,11 @@ pub(crate) struct ImplWindow { pub id: u32, pub title: String, pub app_name: String, + pub pid: u32, pub current_monitor: ImplMonitor, pub x: i32, pub y: i32, + pub z: i32, pub width: u32, pub height: u32, pub is_minimized: bool, @@ -64,11 +67,11 @@ fn get_cf_dictionary_get_value( } } -fn get_cf_number_u32_value(cf_dictionary_ref: CFDictionaryRef, key: &str) -> XCapResult { +fn get_cf_number_i32_value(cf_dictionary_ref: CFDictionaryRef, key: &str) -> XCapResult { unsafe { let cf_number_ref = get_cf_dictionary_get_value(cf_dictionary_ref, key)?; - let mut value: u32 = 0; + let mut value: i32 = 0; let is_success = CFNumberGetValue( cf_number_ref as CFNumberRef, kCFNumberIntType, @@ -125,8 +128,11 @@ impl ImplWindow { impl_monitors: &[ImplMonitor], window_name: String, window_owner_name: String, + z: i32, ) -> XCapResult { - let id = get_cf_number_u32_value(window_cf_dictionary_ref, "kCGWindowNumber")?; + 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")? as u32; + let cg_rect = get_window_cg_rect(window_cf_dictionary_ref)?; let primary_monitor = ImplMonitor::new(CGDisplay::main().id)?; @@ -162,9 +168,11 @@ impl ImplWindow { id, title: window_name, app_name: window_owner_name, + pid, current_monitor: current_monitor.clone(), x: cg_rect.origin.x as i32, y: cg_rect.origin.y as i32, + z, width: cg_rect.size.width as u32, height: cg_rect.size.height as u32, is_minimized, @@ -177,6 +185,8 @@ impl ImplWindow { let impl_monitors = ImplMonitor::all()?; let mut impl_windows = Vec::new(); + // CGWindowListCopyWindowInfo 返回窗口顺序为从顶层到最底层 + // 即在前面的窗口在数组前面 let box_cf_array_ref = BoxCFArrayRef::new(CGWindowListCopyWindowInfo( kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID, @@ -212,11 +222,11 @@ impl ImplWindow { continue; } - let window_sharing_state = match get_cf_number_u32_value( + let window_sharing_state = match get_cf_number_i32_value( window_cf_dictionary_ref, "kCGWindowSharingState", ) { - Ok(window_sharing_state) => window_sharing_state, + Ok(window_sharing_state) => window_sharing_state as u32, _ => continue, }; @@ -229,6 +239,7 @@ impl ImplWindow { &impl_monitors, window_name.clone(), window_owner_name.clone(), + num_windows as i32 - i as i32 - 1, ) { impl_windows.push(impl_window); } else { @@ -248,70 +259,6 @@ impl ImplWindow { } impl ImplWindow { - pub fn refresh(&mut self) -> XCapResult<()> { - unsafe { - let impl_monitors = ImplMonitor::all()?; - - let box_cf_array_ref = BoxCFArrayRef::new(CGWindowListCopyWindowInfo( - kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, - kCGNullWindowID, - )); - - if box_cf_array_ref.is_null() { - return Err(XCapError::new("Run CGWindowListCopyWindowInfo error")); - } - - let num_windows = CFArrayGetCount(*box_cf_array_ref); - - for i in 0..num_windows { - let window_cf_dictionary_ref = - CFArrayGetValueAtIndex(*box_cf_array_ref, i) as CFDictionaryRef; - - if window_cf_dictionary_ref.is_null() { - continue; - } - - let k_cg_window_number = - get_cf_number_u32_value(window_cf_dictionary_ref, "kCGWindowNumber")?; - - if k_cg_window_number == self.id { - let window_name = - match get_cf_string_value(window_cf_dictionary_ref, "kCGWindowName") { - Ok(window_name) => window_name, - _ => return Err(XCapError::new("Get window name failed")), - }; - - let window_owner_name = - match get_cf_string_value(window_cf_dictionary_ref, "kCGWindowOwnerName") { - Ok(window_owner_name) => window_owner_name, - _ => return Err(XCapError::new("Get window owner name failed")), - }; - - let impl_window = ImplWindow::new( - window_cf_dictionary_ref, - &impl_monitors, - window_name, - window_owner_name, - )?; - - self.id = impl_window.id; - self.title = impl_window.title; - self.app_name = impl_window.app_name; - self.current_monitor = impl_window.current_monitor; - self.x = impl_window.x; - self.y = impl_window.y; - self.width = impl_window.width; - self.height = impl_window.height; - self.is_minimized = impl_window.is_minimized; - self.is_maximized = impl_window.is_maximized; - - return Ok(()); - } - } - - Err(XCapError::new("Not Found window")) - } - } pub fn capture_image(&self) -> XCapResult { capture( CGRect::new( diff --git a/src/window.rs b/src/window.rs index a72e5e2..73a875a 100644 --- a/src/window.rs +++ b/src/window.rs @@ -14,6 +14,7 @@ impl Window { } impl Window { + /// List all windows, sorted by z coordinate. pub fn all() -> XCapResult> { let windows = ImplWindow::all()? .iter() @@ -37,11 +38,9 @@ impl Window { pub fn title(&self) -> &str { &self.impl_window.title } - - #[cfg(target_os = "windows")] /// The window process id - pub fn process_id(&self) -> u32 { - self.impl_window.process_id + pub fn pid(&self) -> u32 { + self.impl_window.pid } /// The window current monitor pub fn current_monitor(&self) -> Monitor { @@ -55,6 +54,10 @@ impl Window { pub fn y(&self) -> i32 { self.impl_window.y } + /// The window z coordinate. + pub fn z(&self) -> i32 { + self.impl_window.z + } /// The window pixel width. pub fn width(&self) -> u32 { self.impl_window.width @@ -74,9 +77,6 @@ impl Window { } impl Window { - pub fn refresh(&mut self) -> XCapResult<()> { - self.impl_window.refresh() - } pub fn capture_image(&self) -> XCapResult { self.impl_window.capture_image() } diff --git a/src/windows/impl_window.rs b/src/windows/impl_window.rs index 116cad4..7fac34b 100644 --- a/src/windows/impl_window.rs +++ b/src/windows/impl_window.rs @@ -37,10 +37,11 @@ pub(crate) struct ImplWindow { pub id: u32, pub title: String, pub app_name: String, - pub process_id: u32, + pub pid: u32, pub current_monitor: ImplMonitor, pub x: i32, pub y: i32, + pub z: i32, pub width: u32, pub height: u32, pub is_minimized: bool, @@ -116,7 +117,7 @@ fn is_valid_window(hwnd: HWND) -> bool { // windows owned by the current process. Consumers should either ensure that // the thread running their message loop never waits on this operation, or use // the option to exclude these windows from the source list. - let lp_dw_process_id = get_process_id(hwnd); + let lp_dw_process_id = get_window_pid(hwnd); if lp_dw_process_id == GetCurrentProcessId() { return false; } @@ -159,12 +160,13 @@ fn is_valid_window(hwnd: HWND) -> bool { } unsafe extern "system" fn enum_windows_proc(hwnd: HWND, state: LPARAM) -> BOOL { - if !is_valid_window(hwnd) { - return TRUE; + let state = Box::leak(Box::from_raw(state.0 as *mut (Vec<(HWND, i32)>, i32))); + + if is_valid_window(hwnd) { + state.0.push((hwnd, state.1)); } - let state = Box::leak(Box::from_raw(state.0 as *mut Vec)); - state.push(hwnd); + state.1 += 1; TRUE } @@ -200,7 +202,7 @@ fn get_module_basename(box_process_handle: BoxProcessHandle) -> XCapResult u32 { +fn get_window_pid(hwnd: HWND) -> u32 { unsafe { let mut lp_dw_process_id = 0; GetWindowThreadProcessId(hwnd, Some(&mut lp_dw_process_id)); @@ -208,18 +210,15 @@ fn get_process_id(hwnd: HWND) -> u32 { } } -fn get_app_name(hwnd: HWND) -> XCapResult { +fn get_app_name(pid: u32) -> XCapResult { unsafe { - let lp_dw_process_id = get_process_id(hwnd); - - let box_process_handle = - match BoxProcessHandle::open(PROCESS_ALL_ACCESS, false, lp_dw_process_id) { - Ok(box_handle) => box_handle, - Err(err) => { - log::error!("{}", err); - return Ok(String::new()); - } - }; + let box_process_handle = match BoxProcessHandle::open(PROCESS_ALL_ACCESS, 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); @@ -303,7 +302,7 @@ fn get_app_name(hwnd: HWND) -> XCapResult { } impl ImplWindow { - fn new(hwnd: HWND) -> XCapResult { + fn new(hwnd: HWND, z: i32) -> XCapResult { unsafe { let mut window_info = WINDOWINFO { cbSize: mem::size_of::() as u32, @@ -313,7 +312,8 @@ impl ImplWindow { GetWindowInfo(hwnd, &mut window_info)?; let title = get_window_title(hwnd)?; - let app_name = get_app_name(hwnd)?; + let pid = get_window_pid(hwnd); + let app_name = get_app_name(pid)?; let hmonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); let rc_client = window_info.rcClient; @@ -326,10 +326,11 @@ impl ImplWindow { id: hwnd.0 as u32, title, app_name, - process_id: get_process_id(hwnd), + pid, current_monitor: ImplMonitor::new(hmonitor)?, x: rc_client.left, y: rc_client.top, + z, width: (rc_client.right - rc_client.left) as u32, height: (rc_client.bottom - rc_client.top) as u32, is_minimized, @@ -339,17 +340,22 @@ impl ImplWindow { } pub fn all() -> XCapResult> { - let hwnds_mut_ptr: *mut Vec = Box::into_raw(Box::default()); + // (HWND, i32) 表示当前窗口以及层级,既(窗口,层级 z),i32 表示 max_z_order,既最大的窗口的 z 顺序 + // 窗口当前层级为 max_z_order - z + let hwnds_mut_ptr: *mut (Vec<(HWND, i32)>, i32) = Box::into_raw(Box::default()); let hwnds = unsafe { + // EnumWindows 函数按照 Z 顺序遍历顶层窗口,从最顶层的窗口开始,依次向下遍历。 EnumWindows(Some(enum_windows_proc), LPARAM(hwnds_mut_ptr as isize))?; Box::from_raw(hwnds_mut_ptr) }; let mut impl_windows = Vec::new(); - for &hwnd in hwnds.iter() { - if let Ok(impl_window) = ImplWindow::new(hwnd) { + let max_z_order = hwnds.1; + + for &(hwnd, z) in hwnds.0.iter() { + if let Ok(impl_window) = ImplWindow::new(hwnd, max_z_order - z) { impl_windows.push(impl_window); } else { log::error!("ImplWindow::new({:?}) failed", hwnd); @@ -361,26 +367,6 @@ impl ImplWindow { } impl ImplWindow { - pub fn refresh(&mut self) -> XCapResult<()> { - let impl_window = ImplWindow::new(self.hwnd)?; - - self.hwnd = impl_window.hwnd; - self.window_info = impl_window.window_info; - self.id = impl_window.id; - self.title = impl_window.title; - self.app_name = impl_window.app_name; - self.process_id = impl_window.process_id; - self.current_monitor = impl_window.current_monitor; - self.x = impl_window.x; - self.y = impl_window.y; - self.width = impl_window.width; - self.height = impl_window.height; - self.is_minimized = impl_window.is_minimized; - self.is_maximized = impl_window.is_maximized; - - Ok(()) - } - pub fn capture_image(&self) -> XCapResult { // TODO: 在win10之后,不同窗口有不同的dpi,所以可能存在截图不全或者截图有较大空白,实际窗口没有填充满图片 capture_window( 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 06/11] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=20windows=20?= =?UTF-8?q?=E7=AA=97=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) + } +} From 4c61e8da2bc2bc515bc5e2b4305d20a15e8e5b0c Mon Sep 17 00:00:00 2001 From: nashaofu <19303058+nashaofu@users.noreply.github.com> Date: Sat, 4 Jan 2025 22:40:45 +0800 Subject: [PATCH 07/11] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96windows?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=20(#183)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: windows 权限调整 * feat: 优化代码 * chore: 更新版本号 --------- Co-authored-by: nashaofu --- Cargo.toml | 2 +- src/windows/capture.rs | 6 +++--- src/windows/impl_window.rs | 22 +++++++++++++--------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 72bf4a2..a901c62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "xcap" -version = "0.2.0" +version = "0.2.1" 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/src/windows/capture.rs b/src/windows/capture.rs index 9740556..7d715cd 100644 --- a/src/windows/capture.rs +++ b/src/windows/capture.rs @@ -46,7 +46,7 @@ fn to_rgba_image( unsafe { // 读取数据到 buffer 中 - let is_success = GetDIBits( + let is_failed = GetDIBits( *box_hdc_mem, *box_h_bitmap, 0, @@ -56,7 +56,7 @@ fn to_rgba_image( DIB_RGB_COLORS, ) == 0; - if is_success { + if is_failed { return Err(XCapError::new("Get RGBA data failed")); } }; @@ -152,7 +152,7 @@ pub fn capture_window( } if !is_success { - is_success = PrintWindow(hwnd, *box_hdc_mem, PRINT_WINDOW_FLAGS(3)).as_bool(); + is_success = PrintWindow(hwnd, *box_hdc_mem, PRINT_WINDOW_FLAGS(4)).as_bool(); } if !is_success { diff --git a/src/windows/impl_window.rs b/src/windows/impl_window.rs index c0ed62a..e2882fd 100644 --- a/src/windows/impl_window.rs +++ b/src/windows/impl_window.rs @@ -12,7 +12,9 @@ use windows::{ Storage::FileSystem::{GetFileVersionInfoSizeW, GetFileVersionInfoW, VerQueryValueW}, System::{ ProcessStatus::{GetModuleBaseNameW, GetModuleFileNameExW}, - Threading::{GetCurrentProcess, GetCurrentProcessId, PROCESS_ALL_ACCESS}, + Threading::{ + GetCurrentProcess, GetCurrentProcessId, PROCESS_QUERY_LIMITED_INFORMATION, + }, }, UI::WindowsAndMessaging::{ EnumWindows, GetClassNameW, GetWindowInfo, GetWindowLongPtrW, GetWindowTextLengthW, @@ -216,13 +218,14 @@ fn get_window_pid(hwnd: HWND) -> u32 { fn get_app_name(pid: u32) -> XCapResult { unsafe { - let box_process_handle = match BoxProcessHandle::open(PROCESS_ALL_ACCESS, false, pid) { - Ok(box_handle) => box_handle, - Err(err) => { - log::error!("{}", err); - return Ok(String::new()); - } - }; + 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 mut filename = [0; MAX_PATH as usize]; GetModuleFileNameExW(*box_process_handle, None, &mut filename); @@ -374,7 +377,8 @@ impl ImplWindow { pub fn capture_image(&self) -> XCapResult { // 在win10之后,不同窗口有不同的dpi,所以可能存在截图不全或者截图有较大空白,实际窗口没有填充满图片 // 如果窗口不感知dpi,那么就不需要缩放,如果当前进程感知dpi,那么也不需要缩放 - let box_process_handle = BoxProcessHandle::open(PROCESS_ALL_ACCESS, false, self.pid)?; + 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 current_process_is_dpi_awareness = unsafe { get_process_is_dpi_awareness(GetCurrentProcess())? }; From d236975ebba4d141ae0d538b2473716542ca0701 Mon Sep 17 00:00:00 2001 From: nashaofu <19303058+nashaofu@users.noreply.github.com> Date: Mon, 13 Jan 2025 09:28:09 +0800 Subject: [PATCH 08/11] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E7=AA=97=E5=8F=A3=E6=98=AF=E5=90=A6=20focused=20(#184?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 优化代码 * feat: windows 支持获取窗口是否 focused * feat: linux 实现获取窗口是否 focused * feat: macos 实现获取窗口是否 focused * chore: 修改版本号 * fix: 修复命名 --------- Co-authored-by: nashaofu --- Cargo.toml | 7 ++++++- examples/window.rs | 2 +- src/linux/impl_window.rs | 27 +++++++++++++++++++++++++-- src/macos/impl_window.rs | 16 ++++++++++++++-- src/window.rs | 4 ++++ src/windows/capture.rs | 4 ++-- src/windows/impl_window.rs | 9 ++++++--- 7 files changed, 58 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a901c62..59251ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "xcap" -version = "0.2.1" +version = "0.2.2" 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" @@ -23,6 +23,11 @@ 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", +] } [target.'cfg(target_os = "windows")'.dependencies] windows = { version = "0.58", features = [ diff --git a/examples/window.rs b/examples/window.rs index 6fc67d9..1e06884 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -16,7 +16,7 @@ fn main() { window.current_monitor().name(), (window.x(), window.y(), window.z()), (window.width(), window.height()), - (window.is_minimized(), window.is_maximized()) + (window.is_minimized(), window.is_maximized(), window.is_focused()) ); } } diff --git a/src/linux/impl_window.rs b/src/linux/impl_window.rs index 3200b92..61124c6 100644 --- a/src/linux/impl_window.rs +++ b/src/linux/impl_window.rs @@ -28,6 +28,7 @@ pub(crate) struct ImplWindow { pub height: u32, pub is_minimized: bool, pub is_maximized: bool, + pub is_focused: bool, } fn get_atom(conn: &Connection, name: &str) -> XCapResult { @@ -38,7 +39,7 @@ fn get_atom(conn: &Connection, name: &str) -> XCapResult { let atom_reply = conn.wait_for_reply(atom_cookie)?; let atom = atom_reply.atom(); - if atom == ATOM_NONE { + if atom.is_none() { return Err(XCapError::new(format!("{} not supported", name))); } @@ -79,12 +80,29 @@ pub fn get_window_pid(conn: &Connection, window: &Window) -> XCapResult { .copied() } +fn get_active_window_id(conn: &Connection) -> Option { + let active_window_atom = get_atom(conn, "_NET_ACTIVE_WINDOW").ok()?; + let setup = conn.get_setup(); + + for screen in setup.roots() { + let root_window = screen.root(); + let active_window_id = + get_window_property(conn, root_window, active_window_atom, ATOM_NONE, 0, 4).ok()?; + if let Some(&active_window_id) = active_window_id.value::().first() { + return Some(active_window_id); + } + } + + None +} + impl ImplWindow { fn new( conn: &Connection, window: &Window, pid: u32, z: i32, + is_focused: bool, impl_monitors: &Vec, ) -> XCapResult { let title = { @@ -197,6 +215,7 @@ impl ImplWindow { height, is_minimized, is_maximized, + is_focused, }) } @@ -208,6 +227,7 @@ impl ImplWindow { // https://specifications.freedesktop.org/wm-spec/1.5/ar01s03.html#id-1.4.4 // list all windows by stacking order let client_list_atom = get_atom(&conn, "_NET_CLIENT_LIST_STACKING")?; + let active_window_id = get_active_window_id(&conn); let mut impl_windows = Vec::new(); let impl_monitors = ImplMonitor::all()?; @@ -247,7 +267,10 @@ impl ImplWindow { } }; - if let Ok(impl_window) = ImplWindow::new(&conn, client, pid, z, &impl_monitors) + let is_focused = active_window_id.eq(&Some(client.resource_id())); + + if let Ok(impl_window) = + ImplWindow::new(&conn, client, pid, z, is_focused, &impl_monitors) { impl_windows.push(impl_window); } else { diff --git a/src/macos/impl_window.rs b/src/macos/impl_window.rs index 85d9ce2..fec2093 100644 --- a/src/macos/impl_window.rs +++ b/src/macos/impl_window.rs @@ -16,6 +16,7 @@ use core_graphics::{ window::{kCGNullWindowID, kCGWindowSharingNone}, }; use image::RgbaImage; +use objc2_app_kit::NSWorkspace; use crate::{error::XCapResult, XCapError}; @@ -35,6 +36,7 @@ pub(crate) struct ImplWindow { pub height: u32, pub is_minimized: bool, pub is_maximized: bool, + pub is_focused: bool, } unsafe impl Send for ImplWindow {} @@ -129,9 +131,10 @@ impl ImplWindow { window_name: String, window_owner_name: String, z: i32, + focused_app_pid: Option, ) -> 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")? as u32; + let pid = get_cf_number_i32_value(window_cf_dictionary_ref, "kCGWindowOwnerPID")?; let cg_rect = get_window_cg_rect(window_cf_dictionary_ref)?; @@ -164,11 +167,13 @@ impl ImplWindow { let is_minimized = !get_cf_bool_value(window_cf_dictionary_ref, "kCGWindowIsOnscreen")? && !is_maximized; + let is_focused = focused_app_pid.eq(&Some(pid)); + Ok(ImplWindow { id, title: window_name, app_name: window_owner_name, - pid, + pid: pid as u32, current_monitor: current_monitor.clone(), x: cg_rect.origin.x as i32, y: cg_rect.origin.y as i32, @@ -177,12 +182,18 @@ impl ImplWindow { height: cg_rect.size.height as u32, is_minimized, is_maximized, + is_focused, }) } pub fn all() -> XCapResult> { unsafe { let impl_monitors = ImplMonitor::all()?; + let workspace = NSWorkspace::sharedWorkspace(); + let focused_app_pid = workspace + .frontmostApplication() + .map(|focused_app| focused_app.processIdentifier()); + let mut impl_windows = Vec::new(); // CGWindowListCopyWindowInfo 返回窗口顺序为从顶层到最底层 @@ -240,6 +251,7 @@ impl ImplWindow { window_name.clone(), window_owner_name.clone(), num_windows as i32 - i as i32 - 1, + focused_app_pid, ) { impl_windows.push(impl_window); } else { diff --git a/src/window.rs b/src/window.rs index 73a875a..7e66d55 100644 --- a/src/window.rs +++ b/src/window.rs @@ -74,6 +74,10 @@ impl Window { pub fn is_maximized(&self) -> bool { self.impl_window.is_maximized } + /// The window is focused. + pub fn is_focused(&self) -> bool { + self.impl_window.is_focused + } } impl Window { diff --git a/src/windows/capture.rs b/src/windows/capture.rs index 7d715cd..fa101b5 100644 --- a/src/windows/capture.rs +++ b/src/windows/capture.rs @@ -10,8 +10,8 @@ use windows::Win32::{ OBJ_BITMAP, SRCCOPY, }, }, - Storage::Xps::{PrintWindow, PRINT_WINDOW_FLAGS}, - UI::WindowsAndMessaging::{GetDesktopWindow, WINDOWINFO}, + Storage::Xps::{PrintWindow, PRINT_WINDOW_FLAGS, PW_CLIENTONLY}, + UI::WindowsAndMessaging::{GetDesktopWindow, PW_RENDERFULLCONTENT, WINDOWINFO}, }; use crate::error::{XCapError, XCapResult}; diff --git a/src/windows/impl_window.rs b/src/windows/impl_window.rs index e2882fd..8d15500 100644 --- a/src/windows/impl_window.rs +++ b/src/windows/impl_window.rs @@ -17,9 +17,9 @@ use windows::{ }, }, UI::WindowsAndMessaging::{ - EnumWindows, GetClassNameW, GetWindowInfo, GetWindowLongPtrW, GetWindowTextLengthW, - GetWindowTextW, GetWindowThreadProcessId, IsIconic, IsWindow, IsWindowVisible, - IsZoomed, GWL_EXSTYLE, WINDOWINFO, WINDOW_EX_STYLE, WS_EX_TOOLWINDOW, + EnumWindows, GetClassNameW, GetForegroundWindow, GetWindowInfo, GetWindowLongPtrW, + GetWindowTextLengthW, GetWindowTextW, GetWindowThreadProcessId, IsIconic, IsWindow, + IsWindowVisible, IsZoomed, GWL_EXSTYLE, WINDOWINFO, WINDOW_EX_STYLE, WS_EX_TOOLWINDOW, }, }, }; @@ -52,6 +52,7 @@ pub(crate) struct ImplWindow { pub height: u32, pub is_minimized: bool, pub is_maximized: bool, + pub is_focused: bool, } fn is_window_cloaked(hwnd: HWND) -> bool { @@ -326,6 +327,7 @@ impl ImplWindow { let rc_client = window_info.rcClient; let is_minimized = IsIconic(hwnd).as_bool(); let is_maximized = IsZoomed(hwnd).as_bool(); + let is_focused = GetForegroundWindow() == hwnd; Ok(ImplWindow { hwnd, @@ -342,6 +344,7 @@ impl ImplWindow { height: (rc_client.bottom - rc_client.top) as u32, is_minimized, is_maximized, + is_focused, }) } } From a6f508af2f3a112a4a2d68d34ea3b104eb4ff5af Mon Sep 17 00:00:00 2001 From: nashaofu <19303058+nashaofu@users.noreply.github.com> Date: Tue, 4 Feb 2025 11:58:35 +0800 Subject: [PATCH 09/11] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E5=8F=8B=E5=A5=BD=E7=9A=84=E6=98=BE=E7=A4=BA=E5=99=A8?= =?UTF-8?q?=E5=90=8D=E7=A7=B0=20(#188)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 优化windows 代码 * feat: windows 获取友好的显示器名称 * feat: macos切换底层依赖到objc2 * chore: 修改版本号 * chore: 格式化 --------- Co-authored-by: nashaofu --- Cargo.toml | 19 +-- src/error.rs | 18 +-- src/macos/boxed.rs | 31 ----- src/macos/capture.rs | 56 +++++---- src/macos/impl_monitor.rs | 163 +++++++++++++++----------- src/macos/impl_window.rs | 151 +++++++++++------------- src/macos/mod.rs | 1 - src/windows/boxed.rs | 179 ----------------------------- src/windows/capture.rs | 106 +++++++++++------ src/windows/impl_monitor.rs | 91 +++++++++------ src/windows/impl_video_recorder.rs | 33 +++--- src/windows/impl_window.rs | 68 +++++------ src/windows/mod.rs | 1 - src/windows/utils.rs | 160 ++++++++++++++++++++++---- 14 files changed, 531 insertions(+), 546 deletions(-) delete mode 100644 src/macos/boxed.rs delete mode 100644 src/windows/boxed.rs 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")) + } +} From fa60300d6d4e02ac9aefc9719eb524f38e5befb7 Mon Sep 17 00:00:00 2001 From: nashaofu <19303058+nashaofu@users.noreply.github.com> Date: Tue, 4 Feb 2025 20:45:28 +0800 Subject: [PATCH 10/11] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20macos=20Monit?= =?UTF-8?q?or::from=5Fpoint=20=E6=9C=AA=E6=89=BE=E5=88=B0=E7=AA=97?= =?UTF-8?q?=E5=8F=A3=E6=97=B6=E4=BB=8D=E7=84=B6=E8=BF=94=E5=9B=9E=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E9=97=AE=E9=A2=98=20(#189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 修复 macos Monitor::from_point 未找到窗口时仍然返回数据问题 * chore: 修改版本号 --------- Co-authored-by: nashaofu --- Cargo.toml | 2 +- src/macos/impl_monitor.rs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7f512b0..63912bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "xcap" -version = "0.3.0" +version = "0.3.1" 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/src/macos/impl_monitor.rs b/src/macos/impl_monitor.rs index 9ca8404..323a6f8 100644 --- a/src/macos/impl_monitor.rs +++ b/src/macos/impl_monitor.rs @@ -144,10 +144,15 @@ impl ImplMonitor { ))); } + if display_count == 0 { + return Err(XCapError::new("Monitor not found")); + } + if let Some(&display_id) = display_ids.first() { if unsafe { !CGDisplayIsActive(display_id) } { return Err(XCapError::new("Monitor is not active")); } + println!("display_id: {}", display_id); ImplMonitor::new(display_id) } else { Err(XCapError::new("Monitor not found")) From 7661788aa182ac248095161a111648aa15cc7b05 Mon Sep 17 00:00:00 2001 From: Wesley Matos Date: Wed, 5 Feb 2025 01:54:18 -0300 Subject: [PATCH 11/11] feat: set windows as focused when mouse is on it --- src/linux/impl_window.rs | 23 ++++++++++++++++++++++- src/macos/impl_window.rs | 23 ++++++++++++++--------- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/linux/impl_window.rs b/src/linux/impl_window.rs index 61124c6..3b6fb22 100644 --- a/src/linux/impl_window.rs +++ b/src/linux/impl_window.rs @@ -68,6 +68,19 @@ fn get_window_property( Ok(window_property_reply) } +fn get_focused_window(conn: &Connection, root_window: Window) -> XCapResult { + let active_window_atom = get_atom(conn, "_NET_ACTIVE_WINDOW")?; + + let active_window_reply = + get_window_property(conn, root_window, active_window_atom, ATOM_WINDOW, 0, 1)?; + let active_window = active_window_reply + .value::() + .first() + .copied() + .unwrap_or(Window::none()); + Ok(active_window) +} + pub fn get_window_pid(conn: &Connection, window: &Window) -> XCapResult { let wm_pid_atom = get_atom(conn, "_NET_WM_PID")?; @@ -267,7 +280,15 @@ impl ImplWindow { } }; - let is_focused = active_window_id.eq(&Some(client.resource_id())); + let is_focused = { + let setup = conn.get_setup(); + let screen = setup + .roots() + .next() + .ok_or(XCapError::new("No screen found"))?; + let focused_window = get_focused_window(conn, screen.root())?; + focused_window == *window + }; if let Ok(impl_window) = ImplWindow::new(&conn, client, pid, z, is_focused, &impl_monitors) diff --git a/src/macos/impl_window.rs b/src/macos/impl_window.rs index 57fd920..03a961d 100644 --- a/src/macos/impl_window.rs +++ b/src/macos/impl_window.rs @@ -1,14 +1,14 @@ use std::ffi::c_void; 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, + CGDisplayBounds, CGEventCreate, CGEventGetUnflippedLocation, CGEventSourceCreate, + CGEventSourceStateID, CGMainDisplayID, CGRectContainsPoint, CGRectIntersectsRect, CGRectMakeWithDictionaryRepresentation, CGWindowListCopyWindowInfo, CGWindowListOption, }; @@ -78,6 +78,17 @@ fn get_cf_number_i32_value(cf_dictionary: &CFDictionary, key: &str) -> XCapResul } } +fn get_mouse_position() -> CGPoint { + unsafe { + // HIDSystemState = 1 + let source = CGEventSourceCreate(CGEventSourceStateID::HIDSystemState); + let event = CGEventCreate(source.as_deref()); + let position = CGEventGetUnflippedLocation(event.as_deref()); + + position + } +} + 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() }; @@ -117,7 +128,6 @@ impl ImplWindow { window_name: String, window_owner_name: String, z: i32, - focused_app_pid: Option, ) -> XCapResult { let id = get_cf_number_i32_value(window_cf_dictionary, "kCGWindowNumber")? as u32; let pid = get_cf_number_i32_value(window_cf_dictionary, "kCGWindowOwnerPID")?; @@ -154,7 +164,7 @@ impl ImplWindow { let is_minimized = !get_cf_bool_value(window_cf_dictionary, "kCGWindowIsOnscreen")? && !is_maximized; - let is_focused = focused_app_pid.eq(&Some(pid)); + let is_focused = unsafe { CGRectContainsPoint(cg_rect, get_mouse_position()) }; Ok(ImplWindow { id, @@ -176,10 +186,6 @@ impl ImplWindow { pub fn all() -> XCapResult> { unsafe { let impl_monitors = ImplMonitor::all()?; - let workspace = NSWorkspace::sharedWorkspace(); - let focused_app_pid = workspace - .frontmostApplication() - .map(|focused_app| focused_app.processIdentifier()); let mut impl_windows = Vec::new(); @@ -236,7 +242,6 @@ impl ImplWindow { window_name.clone(), window_owner_name.clone(), num_windows as i32 - i as i32 - 1, - focused_app_pid, ) { impl_windows.push(impl_window); } else {