From 1fda7b0aa8760008d0d523ef1ff5d87d30007c64 Mon Sep 17 00:00:00 2001 From: Louis Beaumont Date: Fri, 13 Dec 2024 11:01:43 -0800 Subject: [PATCH 1/9] feat: window focus --- examples/window.rs | 20 +++++++++++--------- src/linux/impl_window.rs | 32 +++++++++++++++++++++++++++++++- src/macos/impl_window.rs | 29 +++++++++++++++++++++++++++++ src/window.rs | 4 ++++ src/windows/impl_window.rs | 8 ++++++++ 5 files changed, 83 insertions(+), 10 deletions(-) diff --git a/examples/window.rs b/examples/window.rs index 2cccebb..2264b57 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -6,18 +6,20 @@ fn main() { let windows = Window::all().unwrap(); println!("Window::all() 运行耗时: {:?}", start.elapsed()); - for window in windows { - println!( - "Window:\n id: {}\n title: {}\n app_name: {}\n monitor: {:?}\n position: {:?}\n size {:?}\n state {:?}\n", - window.id(), - window.title(), - window.app_name(), + loop { + for window in windows.clone() { + println!( + "Window:\n id: {}\n title: {}\n app_name: {}\n monitor: {:?}\n position: {:?}\n size {:?}\n state {:?}\n", + window.id(), + window.title(), + window.app_name(), window.current_monitor().name(), (window.x(), window.y()), (window.width(), window.height()), - (window.is_minimized(), window.is_maximized()) + (window.is_minimized(), window.is_maximized(), window.is_focused()) ); - } + } - println!("运行耗时: {:?}", start.elapsed()); + std::thread::sleep(std::time::Duration::from_secs(1)); + } } diff --git a/src/linux/impl_window.rs b/src/linux/impl_window.rs index 346a1da..c8fe3f9 100644 --- a/src/linux/impl_window.rs +++ b/src/linux/impl_window.rs @@ -4,7 +4,7 @@ use xcb::{ x::{ Atom, Drawable, GetGeometry, GetProperty, GetPropertyReply, InternAtom, QueryPointer, TranslateCoordinates, Window, ATOM_ATOM, ATOM_NONE, ATOM_STRING, ATOM_WM_CLASS, - ATOM_WM_NAME, + ATOM_WM_NAME, ATOM_WINDOW, }, Connection, Xid, }; @@ -26,6 +26,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 { @@ -65,6 +66,26 @@ 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) +} + impl ImplWindow { fn new( conn: &Connection, @@ -167,6 +188,13 @@ impl ImplWindow { ) }; + 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 + }; + Ok(ImplWindow { window: *window, id: window.resource_id(), @@ -179,6 +207,7 @@ impl ImplWindow { height, is_minimized, is_maximized, + is_focused, }) } @@ -251,6 +280,7 @@ impl ImplWindow { self.height = impl_window.height; self.is_minimized = impl_window.is_minimized; self.is_maximized = impl_window.is_maximized; + self.is_focused = impl_window.is_focused; Ok(()) } diff --git a/src/macos/impl_window.rs b/src/macos/impl_window.rs index 8365f80..74afe44 100644 --- a/src/macos/impl_window.rs +++ b/src/macos/impl_window.rs @@ -32,6 +32,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 {} @@ -98,6 +99,28 @@ fn get_cf_bool_value(cf_dictionary_ref: CFDictionaryRef, key: &str) -> XCapResul Ok(unsafe { CFBooleanGetValue(value_ref as CFBooleanRef) }) } +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: i32 = 0; + let is_success = CFNumberGetValue( + cf_number_ref as CFNumberRef, + kCFNumberIntType, + &mut value as *mut _ as *mut c_void, + ); + + if !is_success { + return Err(XCapError::new(format!( + "Get {} CFNumberGetValue failed", + key + ))); + } + + Ok(value) + } +} + fn get_window_cg_rect(window_cf_dictionary_ref: CFDictionaryRef) -> XCapResult { unsafe { let window_bounds_ref = @@ -158,6 +181,10 @@ impl ImplWindow { let is_minimized = !get_cf_bool_value(window_cf_dictionary_ref, "kCGWindowIsOnscreen")? && !is_maximized; + let is_on_screen = get_cf_bool_value(window_cf_dictionary_ref, "kCGWindowIsOnscreen")?; + let window_layer = get_cf_number_i32_value(window_cf_dictionary_ref, "kCGWindowLayer")?; + let is_focused = is_on_screen && window_layer == 0; + Ok(ImplWindow { id, title: window_name, @@ -169,6 +196,7 @@ impl ImplWindow { height: cg_rect.size.height as u32, is_minimized, is_maximized, + is_focused, }) } @@ -304,6 +332,7 @@ impl ImplWindow { self.height = impl_window.height; self.is_minimized = impl_window.is_minimized; self.is_maximized = impl_window.is_maximized; + self.is_focused = impl_window.is_focused; return Ok(()); } diff --git a/src/window.rs b/src/window.rs index a72e5e2..29f1817 100644 --- a/src/window.rs +++ b/src/window.rs @@ -71,6 +71,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/impl_window.rs b/src/windows/impl_window.rs index 116cad4..c76d73c 100644 --- a/src/windows/impl_window.rs +++ b/src/windows/impl_window.rs @@ -45,6 +45,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 { @@ -302,6 +303,10 @@ fn get_app_name(hwnd: HWND) -> XCapResult { } } +fn is_window_focused(hwnd: HWND) -> bool { + unsafe { GetForegroundWindow() == hwnd } +} + impl ImplWindow { fn new(hwnd: HWND) -> XCapResult { unsafe { @@ -319,6 +324,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 = is_window_focused(hwnd); Ok(ImplWindow { hwnd, @@ -334,6 +340,7 @@ impl ImplWindow { height: (rc_client.bottom - rc_client.top) as u32, is_minimized, is_maximized, + is_focused, }) } } @@ -377,6 +384,7 @@ impl ImplWindow { self.height = impl_window.height; self.is_minimized = impl_window.is_minimized; self.is_maximized = impl_window.is_maximized; + self.is_focused = is_window_focused(self.hwnd); Ok(()) } From ab8d3e1421daf8cceb029dcfee04a9f0bfaf2338 Mon Sep 17 00:00:00 2001 From: Louis Beaumont Date: Fri, 13 Dec 2024 13:14:31 -0800 Subject: [PATCH 2/9] refactor: window is focused is mouse in it --- src/macos/impl_window.rs | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/macos/impl_window.rs b/src/macos/impl_window.rs index 74afe44..1f3f1c8 100644 --- a/src/macos/impl_window.rs +++ b/src/macos/impl_window.rs @@ -43,6 +43,10 @@ extern "C" { dict: CFDictionaryRef, rect: &mut CGRect, ) -> CFBooleanRef; + fn CGEventSourceCreate(stateID: i32) -> *mut c_void; + fn CGEventGetUnflippedLocation(event: *mut c_void) -> CGPoint; + fn CGEventCreate(source: *mut c_void) -> *mut c_void; + fn CFRelease(cf: *mut c_void); } fn get_cf_dictionary_get_value( @@ -142,6 +146,18 @@ fn get_window_cg_rect(window_cf_dictionary_ref: CFDictionaryRef) -> XCapResult CGPoint { + unsafe { + // HIDSystemState = 1 + let source = CGEventSourceCreate(1); + let event = CGEventCreate(source); + let position = CGEventGetUnflippedLocation(event); + CFRelease(event); + CFRelease(source); + position + } +} + impl ImplWindow { pub fn new( window_cf_dictionary_ref: CFDictionaryRef, @@ -181,9 +197,10 @@ impl ImplWindow { let is_minimized = !get_cf_bool_value(window_cf_dictionary_ref, "kCGWindowIsOnscreen")? && !is_maximized; - let is_on_screen = get_cf_bool_value(window_cf_dictionary_ref, "kCGWindowIsOnscreen")?; - let window_layer = get_cf_number_i32_value(window_cf_dictionary_ref, "kCGWindowLayer")?; - let is_focused = is_on_screen && window_layer == 0; + let is_focused = { + let mouse_pos = get_mouse_position(); + cg_rect.contains(&mouse_pos) + }; Ok(ImplWindow { id, From 9497121443844b221e9952f7d8e32507b69ac769 Mon Sep 17 00:00:00 2001 From: nashaofu Date: Sat, 14 Dec 2024 08:53:04 +0800 Subject: [PATCH 3/9] fix: fix windows build error --- src/windows/impl_window.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/windows/impl_window.rs b/src/windows/impl_window.rs index c76d73c..66c5ed2 100644 --- a/src/windows/impl_window.rs +++ b/src/windows/impl_window.rs @@ -15,9 +15,9 @@ use windows::{ Threading::{GetCurrentProcessId, PROCESS_ALL_ACCESS}, }, 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, }, }, }; From cfdf7f7249d8cbe51c01a04964dcf9f0d9979db4 Mon Sep 17 00:00:00 2001 From: nashaofu Date: Sat, 14 Dec 2024 09:02:00 +0800 Subject: [PATCH 4/9] fix: formatted code --- src/linux/impl_window.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/linux/impl_window.rs b/src/linux/impl_window.rs index c8fe3f9..eb06613 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, ATOM_WINDOW, + TranslateCoordinates, Window, ATOM_ATOM, ATOM_NONE, ATOM_STRING, ATOM_WINDOW, + ATOM_WM_CLASS, ATOM_WM_NAME, }, Connection, Xid, }; @@ -68,17 +68,12 @@ fn get_window_property( 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::() + + 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()); @@ -190,7 +185,10 @@ impl ImplWindow { let is_focused = { let setup = conn.get_setup(); - let screen = setup.roots().next().ok_or(XCapError::new("No screen found"))?; + let screen = setup + .roots() + .next() + .ok_or(XCapError::new("No screen found"))?; let focused_window = get_focused_window(conn, screen.root())?; focused_window == *window }; From 873afae2d4e8678bda031d86c302b67cf6391820 Mon Sep 17 00:00:00 2001 From: nashaofu Date: Sun, 15 Dec 2024 09:37:14 +0800 Subject: [PATCH 5/9] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=20image=20?= =?UTF-8?q?=E4=B8=8D=E4=BD=9C=E4=B8=BA=E9=BB=98=E8=AE=A4feature?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" From a4164295a8c5c37022494e0c9d1ad68f2ebe948c Mon Sep 17 00:00:00 2001 From: nashaofu Date: Tue, 17 Dec 2024 09:42:19 +0800 Subject: [PATCH 6/9] =?UTF-8?q?feat:=20macos=20=E5=AE=9E=E7=8E=B0=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E7=AA=97=E5=8F=A3=E6=98=AF=E5=90=A6=E8=81=9A=E7=84=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 5 +++ examples/window.rs | 14 +++--- src/macos/impl_window.rs | 92 +++++++++++++++++++++++----------------- 3 files changed, 63 insertions(+), 48 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bc088b5..2e85470 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,11 @@ log = "0.4" thiserror = "2.0" [target.'cfg(target_os = "macos")'.dependencies] +objc2-app-kit = { version = "0.2", features = [ + "libc", + "NSRunningApplication", + "NSWorkspace", +] } core-foundation = "0.10" core-graphics = "0.24" diff --git a/examples/window.rs b/examples/window.rs index 2264b57..eaaea0e 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -1,14 +1,13 @@ -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()); - loop { - for window in windows.clone() { - println!( + for window in windows.clone() { + println!( "Window:\n id: {}\n title: {}\n app_name: {}\n monitor: {:?}\n position: {:?}\n size {:?}\n state {:?}\n", window.id(), window.title(), @@ -18,8 +17,5 @@ fn main() { (window.width(), window.height()), (window.is_minimized(), window.is_maximized(), window.is_focused()) ); - } - - std::thread::sleep(std::time::Duration::from_secs(1)); } } diff --git a/src/macos/impl_window.rs b/src/macos/impl_window.rs index 1f3f1c8..9dc455c 100644 --- a/src/macos/impl_window.rs +++ b/src/macos/impl_window.rs @@ -14,6 +14,7 @@ use core_graphics::{ window::{kCGNullWindowID, kCGWindowSharingNone}, }; use image::RgbaImage; +use objc2_app_kit::NSWorkspace; use std::ffi::c_void; use crate::{error::XCapResult, XCapError}; @@ -43,10 +44,6 @@ extern "C" { dict: CFDictionaryRef, rect: &mut CGRect, ) -> CFBooleanRef; - fn CGEventSourceCreate(stateID: i32) -> *mut c_void; - fn CGEventGetUnflippedLocation(event: *mut c_void) -> CGPoint; - fn CGEventCreate(source: *mut c_void) -> *mut c_void; - fn CFRelease(cf: *mut c_void); } fn get_cf_dictionary_get_value( @@ -103,28 +100,6 @@ fn get_cf_bool_value(cf_dictionary_ref: CFDictionaryRef, key: &str) -> XCapResul Ok(unsafe { CFBooleanGetValue(value_ref as CFBooleanRef) }) } -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: i32 = 0; - let is_success = CFNumberGetValue( - cf_number_ref as CFNumberRef, - kCFNumberIntType, - &mut value as *mut _ as *mut c_void, - ); - - if !is_success { - return Err(XCapError::new(format!( - "Get {} CFNumberGetValue failed", - key - ))); - } - - Ok(value) - } -} - fn get_window_cg_rect(window_cf_dictionary_ref: CFDictionaryRef) -> XCapResult { unsafe { let window_bounds_ref = @@ -146,15 +121,12 @@ fn get_window_cg_rect(window_cf_dictionary_ref: CFDictionaryRef) -> XCapResult CGPoint { +fn get_focused_app_pid() -> Option { unsafe { - // HIDSystemState = 1 - let source = CGEventSourceCreate(1); - let event = CGEventCreate(source); - let position = CGEventGetUnflippedLocation(event); - CFRelease(event); - CFRelease(source); - position + let ns_workspace = NSWorkspace::sharedWorkspace(); + let frontmost_application = ns_workspace.frontmostApplication()?; + + Some(frontmost_application.processIdentifier() as u32) } } @@ -164,6 +136,7 @@ impl ImplWindow { impl_monitors: &[ImplMonitor], window_name: String, window_owner_name: String, + is_focused: bool, ) -> XCapResult { let id = get_cf_number_u32_value(window_cf_dictionary_ref, "kCGWindowNumber")?; let cg_rect = get_window_cg_rect(window_cf_dictionary_ref)?; @@ -197,11 +170,6 @@ impl ImplWindow { let is_minimized = !get_cf_bool_value(window_cf_dictionary_ref, "kCGWindowIsOnscreen")? && !is_maximized; - let is_focused = { - let mouse_pos = get_mouse_position(); - cg_rect.contains(&mouse_pos) - }; - Ok(ImplWindow { id, title: window_name, @@ -221,7 +189,12 @@ impl ImplWindow { unsafe { let impl_monitors = ImplMonitor::all()?; let mut impl_windows = Vec::new(); + let focused_app_pid = get_focused_app_pid(); + // 是否已经设置过焦点窗口 + let mut is_set_focused_window = false; + // CGWindowListCopyWindowInfo 返回窗口顺序为从顶层到最底层 + // 即在前面的窗口在数组前面 let box_cf_array_ref = BoxCFArrayRef::new(CGWindowListCopyWindowInfo( kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID, @@ -269,11 +242,30 @@ impl ImplWindow { continue; } + let window_owner_pid = + match get_cf_number_u32_value(window_cf_dictionary_ref, "kCGWindowOwnerPID") { + Ok(window_owner_pid) => Some(window_owner_pid), + _ => None, + }; + + let is_focused = if focused_app_pid.is_some() && window_owner_pid == focused_app_pid + { + if !is_set_focused_window { + is_set_focused_window = true; + true + } else { + false + } + } else { + false + }; + if let Ok(impl_window) = ImplWindow::new( window_cf_dictionary_ref, &impl_monitors, window_name.clone(), window_owner_name.clone(), + is_focused, ) { impl_windows.push(impl_window); } else { @@ -296,6 +288,9 @@ impl ImplWindow { pub fn refresh(&mut self) -> XCapResult<()> { unsafe { let impl_monitors = ImplMonitor::all()?; + let focused_app_pid = get_focused_app_pid(); + // 是否已经设置过焦点窗口 + let mut is_set_focused_window = false; let box_cf_array_ref = BoxCFArrayRef::new(CGWindowListCopyWindowInfo( kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, @@ -319,6 +314,24 @@ impl ImplWindow { let k_cg_window_number = get_cf_number_u32_value(window_cf_dictionary_ref, "kCGWindowNumber")?; + let window_owner_pid = + match get_cf_number_u32_value(window_cf_dictionary_ref, "kCGWindowOwnerPID") { + Ok(window_owner_pid) => Some(window_owner_pid), + _ => None, + }; + + let is_focused = if focused_app_pid.is_some() && window_owner_pid == focused_app_pid + { + if !is_set_focused_window { + is_set_focused_window = true; + true + } else { + false + } + } else { + false + }; + if k_cg_window_number == self.id { let window_name = match get_cf_string_value(window_cf_dictionary_ref, "kCGWindowName") { @@ -337,6 +350,7 @@ impl ImplWindow { &impl_monitors, window_name, window_owner_name, + is_focused, )?; self.id = impl_window.id; From c904b1e470830925c7046437091c03cf3d0847dd Mon Sep 17 00:00:00 2001 From: nashaofu <19303058+nashaofu@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:48:46 +0800 Subject: [PATCH 7/9] feat(breaking): List windows in z order (#178) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 窗口按照z轴顺序返回 * refactor(BREAKING CHANGE): remove window.refresh() --------- Co-authored-by: nashaofu --- Cargo.toml | 5 -- examples/monitor_record.rs | 63 +++++-------- examples/window.rs | 7 +- examples/window_record.rs | 70 --------------- examples/windows_monitor_record.rs | 30 ------- src/linux/impl_window.rs | 76 +++++++--------- src/macos/impl_window.rs | 140 +++-------------------------- src/window.rs | 18 ++-- src/windows/impl_window.rs | 88 +++++++----------- 9 files changed, 111 insertions(+), 386 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 2e85470..bc088b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,11 +21,6 @@ log = "0.4" thiserror = "2.0" [target.'cfg(target_os = "macos")'.dependencies] -objc2-app-kit = { version = "0.2", features = [ - "libc", - "NSRunningApplication", - "NSWorkspace", -] } core-foundation = "0.10" core-graphics = "0.24" 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 eaaea0e..a50ef6a 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -8,14 +8,15 @@ fn main() { 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(), window.is_focused()) + (window.is_minimized(), window.is_maximized()) ); } } 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 eb06613..3200b92 100644 --- a/src/linux/impl_window.rs +++ b/src/linux/impl_window.rs @@ -3,7 +3,7 @@ use std::str; use xcb::{ x::{ Atom, Drawable, GetGeometry, GetProperty, GetPropertyReply, InternAtom, QueryPointer, - TranslateCoordinates, Window, ATOM_ATOM, ATOM_NONE, ATOM_STRING, ATOM_WINDOW, + TranslateCoordinates, Window, ATOM_ATOM, ATOM_CARDINAL, ATOM_NONE, ATOM_STRING, ATOM_WM_CLASS, ATOM_WM_NAME, }, Connection, Xid, @@ -19,14 +19,15 @@ 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, pub is_maximized: bool, - pub is_focused: bool, } fn get_atom(conn: &Connection, name: &str) -> XCapResult { @@ -66,25 +67,24 @@ 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")?; +pub fn get_window_pid(conn: &Connection, window: &Window) -> XCapResult { + let wm_pid_atom = get_atom(conn, "_NET_WM_PID")?; - let active_window_reply = - get_window_property(conn, root_window, active_window_atom, ATOM_WINDOW, 0, 1)?; + let reply = get_window_property(conn, *window, wm_pid_atom, ATOM_CARDINAL, 0, 4)?; + let value = reply.value::(); - let active_window = active_window_reply - .value::() + value .first() + .ok_or(XCapError::new("Get window pid failed")) .copied() - .unwrap_or(Window::none()); - - Ok(active_window) } impl ImplWindow { fn new( conn: &Connection, window: &Window, + pid: u32, + z: i32, impl_monitors: &Vec, ) -> XCapResult { let title = { @@ -183,29 +183,20 @@ impl ImplWindow { ) }; - 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 - }; - Ok(ImplWindow { window: *window, id: window.resource_id(), title, app_name, + pid, current_monitor, x, y, + z, width, height, is_minimized, is_maximized, - is_focused, }) } @@ -214,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(); @@ -237,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!( @@ -257,31 +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; - self.is_focused = impl_window.is_focused; - - 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 9dc455c..39e958d 100644 --- a/src/macos/impl_window.rs +++ b/src/macos/impl_window.rs @@ -14,7 +14,6 @@ use core_graphics::{ window::{kCGNullWindowID, kCGWindowSharingNone}, }; use image::RgbaImage; -use objc2_app_kit::NSWorkspace; use std::ffi::c_void; use crate::{error::XCapResult, XCapError}; @@ -26,14 +25,15 @@ 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, pub is_maximized: bool, - pub is_focused: bool, } unsafe impl Send for ImplWindow {} @@ -66,11 +66,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, @@ -121,26 +121,20 @@ fn get_window_cg_rect(window_cf_dictionary_ref: CFDictionaryRef) -> XCapResult Option { - unsafe { - let ns_workspace = NSWorkspace::sharedWorkspace(); - let frontmost_application = ns_workspace.frontmostApplication()?; - - Some(frontmost_application.processIdentifier() as u32) - } -} - impl ImplWindow { pub fn new( window_cf_dictionary_ref: CFDictionaryRef, impl_monitors: &[ImplMonitor], window_name: String, window_owner_name: String, - is_focused: bool, ) -> 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 window_layer = get_cf_number_i32_value(window_cf_dictionary_ref, "kCGWindowLayer")?; + let primary_monitor = ImplMonitor::new(CGDisplay::main().id)?; let (is_maximized, current_monitor) = { @@ -174,14 +168,15 @@ 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: window_layer, width: cg_rect.size.width as u32, height: cg_rect.size.height as u32, is_minimized, is_maximized, - is_focused, }) } @@ -189,9 +184,6 @@ impl ImplWindow { unsafe { let impl_monitors = ImplMonitor::all()?; let mut impl_windows = Vec::new(); - let focused_app_pid = get_focused_app_pid(); - // 是否已经设置过焦点窗口 - let mut is_set_focused_window = false; // CGWindowListCopyWindowInfo 返回窗口顺序为从顶层到最底层 // 即在前面的窗口在数组前面 @@ -230,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, }; @@ -242,30 +234,11 @@ impl ImplWindow { continue; } - let window_owner_pid = - match get_cf_number_u32_value(window_cf_dictionary_ref, "kCGWindowOwnerPID") { - Ok(window_owner_pid) => Some(window_owner_pid), - _ => None, - }; - - let is_focused = if focused_app_pid.is_some() && window_owner_pid == focused_app_pid - { - if !is_set_focused_window { - is_set_focused_window = true; - true - } else { - false - } - } else { - false - }; - if let Ok(impl_window) = ImplWindow::new( window_cf_dictionary_ref, &impl_monitors, window_name.clone(), window_owner_name.clone(), - is_focused, ) { impl_windows.push(impl_window); } else { @@ -285,93 +258,6 @@ impl ImplWindow { } impl ImplWindow { - pub fn refresh(&mut self) -> XCapResult<()> { - unsafe { - let impl_monitors = ImplMonitor::all()?; - let focused_app_pid = get_focused_app_pid(); - // 是否已经设置过焦点窗口 - let mut is_set_focused_window = false; - - 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")?; - - let window_owner_pid = - match get_cf_number_u32_value(window_cf_dictionary_ref, "kCGWindowOwnerPID") { - Ok(window_owner_pid) => Some(window_owner_pid), - _ => None, - }; - - let is_focused = if focused_app_pid.is_some() && window_owner_pid == focused_app_pid - { - if !is_set_focused_window { - is_set_focused_window = true; - true - } else { - false - } - } else { - false - }; - - 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, - is_focused, - )?; - - 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; - self.is_focused = impl_window.is_focused; - - 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 29f1817..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 @@ -71,16 +74,9 @@ 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 { - 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 66c5ed2..7fac34b 100644 --- a/src/windows/impl_window.rs +++ b/src/windows/impl_window.rs @@ -15,9 +15,9 @@ use windows::{ Threading::{GetCurrentProcessId, PROCESS_ALL_ACCESS}, }, UI::WindowsAndMessaging::{ - EnumWindows, GetClassNameW, GetForegroundWindow, GetWindowInfo, GetWindowLongPtrW, - GetWindowTextLengthW, GetWindowTextW, GetWindowThreadProcessId, IsIconic, IsWindow, - IsWindowVisible, IsZoomed, GWL_EXSTYLE, WINDOWINFO, WINDOW_EX_STYLE, WS_EX_TOOLWINDOW, + EnumWindows, GetClassNameW, GetWindowInfo, GetWindowLongPtrW, GetWindowTextLengthW, + GetWindowTextW, GetWindowThreadProcessId, IsIconic, IsWindow, IsWindowVisible, + IsZoomed, GWL_EXSTYLE, WINDOWINFO, WINDOW_EX_STYLE, WS_EX_TOOLWINDOW, }, }, }; @@ -37,15 +37,15 @@ 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, pub is_maximized: bool, - pub is_focused: bool, } fn is_window_cloaked(hwnd: HWND) -> bool { @@ -117,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; } @@ -160,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 } @@ -201,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)); @@ -209,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,12 +301,8 @@ fn get_app_name(hwnd: HWND) -> XCapResult { } } -fn is_window_focused(hwnd: HWND) -> bool { - unsafe { GetForegroundWindow() == hwnd } -} - 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, @@ -318,13 +312,13 @@ 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; let is_minimized = IsIconic(hwnd).as_bool(); let is_maximized = IsZoomed(hwnd).as_bool(); - let is_focused = is_window_focused(hwnd); Ok(ImplWindow { hwnd, @@ -332,31 +326,36 @@ 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, is_maximized, - is_focused, }) } } 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); @@ -368,27 +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; - self.is_focused = is_window_focused(self.hwnd); - - Ok(()) - } - pub fn capture_image(&self) -> XCapResult { // TODO: 在win10之后,不同窗口有不同的dpi,所以可能存在截图不全或者截图有较大空白,实际窗口没有填充满图片 capture_window( From afebfd6602b8b53319fbec1079fbc16ae98de576 Mon Sep 17 00:00:00 2001 From: nashaofu Date: Fri, 20 Dec 2024 21:51:57 +0800 Subject: [PATCH 8/9] chore: fmt --- examples/window.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/window.rs b/examples/window.rs index a50ef6a..6fc67d9 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -8,11 +8,11 @@ fn main() { for window in windows.clone() { println!( - "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:\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.z()), (window.width(), window.height()), From d3d19501909b157588789a6554b0b9beff3d9f66 Mon Sep 17 00:00:00 2001 From: nashaofu <19303058+nashaofu@users.noreply.github.com> Date: Sat, 21 Dec 2024 22:45:03 +0800 Subject: [PATCH 9/9] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=20macos=20?= =?UTF-8?q?=E7=AA=97=E5=8F=A3=20z=20=E5=80=BC=20(#179)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: nashaofu --- src/macos/impl_window.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/macos/impl_window.rs b/src/macos/impl_window.rs index 39e958d..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}; @@ -127,14 +128,13 @@ impl ImplWindow { impl_monitors: &[ImplMonitor], window_name: String, window_owner_name: String, + z: i32, ) -> 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 cg_rect = get_window_cg_rect(window_cf_dictionary_ref)?; - let window_layer = get_cf_number_i32_value(window_cf_dictionary_ref, "kCGWindowLayer")?; - let primary_monitor = ImplMonitor::new(CGDisplay::main().id)?; let (is_maximized, current_monitor) = { @@ -172,7 +172,7 @@ impl ImplWindow { current_monitor: current_monitor.clone(), x: cg_rect.origin.x as i32, y: cg_rect.origin.y as i32, - z: window_layer, + z, width: cg_rect.size.width as u32, height: cg_rect.size.height as u32, is_minimized, @@ -239,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 {