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] 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(