Skip to content

Commit d236975

Browse files
authored
feat: 支持获取窗口是否 focused (#184)
* feat: 优化代码 * feat: windows 支持获取窗口是否 focused * feat: linux 实现获取窗口是否 focused * feat: macos 实现获取窗口是否 focused * chore: 修改版本号 * fix: 修复命名 --------- Co-authored-by: nashaofu <[email protected]>
1 parent 4c61e8d commit d236975

File tree

7 files changed

+58
-11
lines changed

7 files changed

+58
-11
lines changed

Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "xcap"
3-
version = "0.2.1"
3+
version = "0.2.2"
44
edition = "2021"
55
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)."
66
license = "Apache-2.0"
@@ -23,6 +23,11 @@ thiserror = "2.0"
2323
[target.'cfg(target_os = "macos")'.dependencies]
2424
core-foundation = "0.10"
2525
core-graphics = "0.24"
26+
objc2-app-kit = { version = "0.2.2", features = [
27+
"libc",
28+
"NSWorkspace",
29+
"NSRunningApplication",
30+
] }
2631

2732
[target.'cfg(target_os = "windows")'.dependencies]
2833
windows = { version = "0.58", features = [

examples/window.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ fn main() {
1616
window.current_monitor().name(),
1717
(window.x(), window.y(), window.z()),
1818
(window.width(), window.height()),
19-
(window.is_minimized(), window.is_maximized())
19+
(window.is_minimized(), window.is_maximized(), window.is_focused())
2020
);
2121
}
2222
}

src/linux/impl_window.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub(crate) struct ImplWindow {
2828
pub height: u32,
2929
pub is_minimized: bool,
3030
pub is_maximized: bool,
31+
pub is_focused: bool,
3132
}
3233

3334
fn get_atom(conn: &Connection, name: &str) -> XCapResult<Atom> {
@@ -38,7 +39,7 @@ fn get_atom(conn: &Connection, name: &str) -> XCapResult<Atom> {
3839
let atom_reply = conn.wait_for_reply(atom_cookie)?;
3940
let atom = atom_reply.atom();
4041

41-
if atom == ATOM_NONE {
42+
if atom.is_none() {
4243
return Err(XCapError::new(format!("{} not supported", name)));
4344
}
4445

@@ -79,12 +80,29 @@ pub fn get_window_pid(conn: &Connection, window: &Window) -> XCapResult<u32> {
7980
.copied()
8081
}
8182

83+
fn get_active_window_id(conn: &Connection) -> Option<u32> {
84+
let active_window_atom = get_atom(conn, "_NET_ACTIVE_WINDOW").ok()?;
85+
let setup = conn.get_setup();
86+
87+
for screen in setup.roots() {
88+
let root_window = screen.root();
89+
let active_window_id =
90+
get_window_property(conn, root_window, active_window_atom, ATOM_NONE, 0, 4).ok()?;
91+
if let Some(&active_window_id) = active_window_id.value::<u32>().first() {
92+
return Some(active_window_id);
93+
}
94+
}
95+
96+
None
97+
}
98+
8299
impl ImplWindow {
83100
fn new(
84101
conn: &Connection,
85102
window: &Window,
86103
pid: u32,
87104
z: i32,
105+
is_focused: bool,
88106
impl_monitors: &Vec<ImplMonitor>,
89107
) -> XCapResult<ImplWindow> {
90108
let title = {
@@ -197,6 +215,7 @@ impl ImplWindow {
197215
height,
198216
is_minimized,
199217
is_maximized,
218+
is_focused,
200219
})
201220
}
202221

@@ -208,6 +227,7 @@ impl ImplWindow {
208227
// https://specifications.freedesktop.org/wm-spec/1.5/ar01s03.html#id-1.4.4
209228
// list all windows by stacking order
210229
let client_list_atom = get_atom(&conn, "_NET_CLIENT_LIST_STACKING")?;
230+
let active_window_id = get_active_window_id(&conn);
211231

212232
let mut impl_windows = Vec::new();
213233
let impl_monitors = ImplMonitor::all()?;
@@ -247,7 +267,10 @@ impl ImplWindow {
247267
}
248268
};
249269

250-
if let Ok(impl_window) = ImplWindow::new(&conn, client, pid, z, &impl_monitors)
270+
let is_focused = active_window_id.eq(&Some(client.resource_id()));
271+
272+
if let Ok(impl_window) =
273+
ImplWindow::new(&conn, client, pid, z, is_focused, &impl_monitors)
251274
{
252275
impl_windows.push(impl_window);
253276
} else {

src/macos/impl_window.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use core_graphics::{
1616
window::{kCGNullWindowID, kCGWindowSharingNone},
1717
};
1818
use image::RgbaImage;
19+
use objc2_app_kit::NSWorkspace;
1920

2021
use crate::{error::XCapResult, XCapError};
2122

@@ -35,6 +36,7 @@ pub(crate) struct ImplWindow {
3536
pub height: u32,
3637
pub is_minimized: bool,
3738
pub is_maximized: bool,
39+
pub is_focused: bool,
3840
}
3941

4042
unsafe impl Send for ImplWindow {}
@@ -129,9 +131,10 @@ impl ImplWindow {
129131
window_name: String,
130132
window_owner_name: String,
131133
z: i32,
134+
focused_app_pid: Option<i32>,
132135
) -> XCapResult<ImplWindow> {
133136
let id = get_cf_number_i32_value(window_cf_dictionary_ref, "kCGWindowNumber")? as u32;
134-
let pid = get_cf_number_i32_value(window_cf_dictionary_ref, "kCGWindowOwnerPID")? as u32;
137+
let pid = get_cf_number_i32_value(window_cf_dictionary_ref, "kCGWindowOwnerPID")?;
135138

136139
let cg_rect = get_window_cg_rect(window_cf_dictionary_ref)?;
137140

@@ -164,11 +167,13 @@ impl ImplWindow {
164167
let is_minimized =
165168
!get_cf_bool_value(window_cf_dictionary_ref, "kCGWindowIsOnscreen")? && !is_maximized;
166169

170+
let is_focused = focused_app_pid.eq(&Some(pid));
171+
167172
Ok(ImplWindow {
168173
id,
169174
title: window_name,
170175
app_name: window_owner_name,
171-
pid,
176+
pid: pid as u32,
172177
current_monitor: current_monitor.clone(),
173178
x: cg_rect.origin.x as i32,
174179
y: cg_rect.origin.y as i32,
@@ -177,12 +182,18 @@ impl ImplWindow {
177182
height: cg_rect.size.height as u32,
178183
is_minimized,
179184
is_maximized,
185+
is_focused,
180186
})
181187
}
182188

183189
pub fn all() -> XCapResult<Vec<ImplWindow>> {
184190
unsafe {
185191
let impl_monitors = ImplMonitor::all()?;
192+
let workspace = NSWorkspace::sharedWorkspace();
193+
let focused_app_pid = workspace
194+
.frontmostApplication()
195+
.map(|focused_app| focused_app.processIdentifier());
196+
186197
let mut impl_windows = Vec::new();
187198

188199
// CGWindowListCopyWindowInfo 返回窗口顺序为从顶层到最底层
@@ -240,6 +251,7 @@ impl ImplWindow {
240251
window_name.clone(),
241252
window_owner_name.clone(),
242253
num_windows as i32 - i as i32 - 1,
254+
focused_app_pid,
243255
) {
244256
impl_windows.push(impl_window);
245257
} else {

src/window.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ impl Window {
7474
pub fn is_maximized(&self) -> bool {
7575
self.impl_window.is_maximized
7676
}
77+
/// The window is focused.
78+
pub fn is_focused(&self) -> bool {
79+
self.impl_window.is_focused
80+
}
7781
}
7882

7983
impl Window {

src/windows/capture.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ use windows::Win32::{
1010
OBJ_BITMAP, SRCCOPY,
1111
},
1212
},
13-
Storage::Xps::{PrintWindow, PRINT_WINDOW_FLAGS},
14-
UI::WindowsAndMessaging::{GetDesktopWindow, WINDOWINFO},
13+
Storage::Xps::{PrintWindow, PRINT_WINDOW_FLAGS, PW_CLIENTONLY},
14+
UI::WindowsAndMessaging::{GetDesktopWindow, PW_RENDERFULLCONTENT, WINDOWINFO},
1515
};
1616

1717
use crate::error::{XCapError, XCapResult};

src/windows/impl_window.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ use windows::{
1717
},
1818
},
1919
UI::WindowsAndMessaging::{
20-
EnumWindows, GetClassNameW, GetWindowInfo, GetWindowLongPtrW, GetWindowTextLengthW,
21-
GetWindowTextW, GetWindowThreadProcessId, IsIconic, IsWindow, IsWindowVisible,
22-
IsZoomed, GWL_EXSTYLE, WINDOWINFO, WINDOW_EX_STYLE, WS_EX_TOOLWINDOW,
20+
EnumWindows, GetClassNameW, GetForegroundWindow, GetWindowInfo, GetWindowLongPtrW,
21+
GetWindowTextLengthW, GetWindowTextW, GetWindowThreadProcessId, IsIconic, IsWindow,
22+
IsWindowVisible, IsZoomed, GWL_EXSTYLE, WINDOWINFO, WINDOW_EX_STYLE, WS_EX_TOOLWINDOW,
2323
},
2424
},
2525
};
@@ -52,6 +52,7 @@ pub(crate) struct ImplWindow {
5252
pub height: u32,
5353
pub is_minimized: bool,
5454
pub is_maximized: bool,
55+
pub is_focused: bool,
5556
}
5657

5758
fn is_window_cloaked(hwnd: HWND) -> bool {
@@ -326,6 +327,7 @@ impl ImplWindow {
326327
let rc_client = window_info.rcClient;
327328
let is_minimized = IsIconic(hwnd).as_bool();
328329
let is_maximized = IsZoomed(hwnd).as_bool();
330+
let is_focused = GetForegroundWindow() == hwnd;
329331

330332
Ok(ImplWindow {
331333
hwnd,
@@ -342,6 +344,7 @@ impl ImplWindow {
342344
height: (rc_client.bottom - rc_client.top) as u32,
343345
is_minimized,
344346
is_maximized,
347+
is_focused,
345348
})
346349
}
347350
}

0 commit comments

Comments
 (0)