Skip to content

Commit

Permalink
fix: get is_focused value dynamically (#191)
Browse files Browse the repository at this point in the history
* feat: write example to test against

* feat: use `activeApplication` to get focused app

* refactor: leave window creation out of the loop

* refactor(mac): make `is_focused` a computed function

* refactor(linux): make `is_focused` a computed function

* refactor(windows): make `is_focused` a computed function

* fix(windows): add struct closing
  • Loading branch information
ologbonowiwi authored Feb 14, 2025
1 parent fa60300 commit 214cb44
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 25 deletions.
25 changes: 25 additions & 0 deletions examples/focused_window.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use std::thread;
use xcap::Window;

fn main() {
thread::sleep(std::time::Duration::from_secs(3));

let windows = Window::all().unwrap();

loop {
windows.iter().filter(|w| w.is_focused()).for_each(|focused| {
println!(
"Focused Window:\n id: {}\n title: {}\n app_name: {}\n monitor: {:?}\n position: {:?}\n size {:?}\n state {:?}\n",
focused.id(),
focused.title(),
focused.app_name(),
focused.current_monitor().name(),
(focused.x(), focused.y(), focused.z()),
(focused.width(), focused.height()),
(focused.is_minimized(), focused.is_maximized(), focused.is_focused())
);
});

thread::sleep(std::time::Duration::from_secs(1));
}
}
22 changes: 11 additions & 11 deletions src/linux/impl_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ 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<Atom> {
Expand Down Expand Up @@ -80,14 +79,15 @@ pub fn get_window_pid(conn: &Connection, window: &Window) -> XCapResult<u32> {
.copied()
}

fn get_active_window_id(conn: &Connection) -> Option<u32> {
let active_window_atom = get_atom(conn, "_NET_ACTIVE_WINDOW").ok()?;
fn get_active_window_id() -> Option<u32> {
let (conn, _) = Connection::connect(None).ok()?;
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()?;
get_window_property(&conn, root_window, active_window_atom, ATOM_NONE, 0, 4).ok()?;
if let Some(&active_window_id) = active_window_id.value::<u32>().first() {
return Some(active_window_id);
}
Expand All @@ -102,7 +102,6 @@ impl ImplWindow {
window: &Window,
pid: u32,
z: i32,
is_focused: bool,
impl_monitors: &Vec<ImplMonitor>,
) -> XCapResult<ImplWindow> {
let title = {
Expand Down Expand Up @@ -215,10 +214,15 @@ impl ImplWindow {
height,
is_minimized,
is_maximized,
is_focused,
})
}

pub fn is_focused(&self) -> bool {
let active_window_id = get_active_window_id();

active_window_id.eq(&Some(self.pid))
}

pub fn all() -> XCapResult<Vec<ImplWindow>> {
let (conn, _) = Connection::connect(None)?;
let setup = conn.get_setup();
Expand All @@ -227,7 +231,6 @@ 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()?;
Expand Down Expand Up @@ -267,10 +270,7 @@ impl ImplWindow {
}
};

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)
if let Ok(impl_window) = ImplWindow::new(&conn, client, pid, z, &impl_monitors)
{
impl_windows.push(impl_window);
} else {
Expand Down
25 changes: 15 additions & 10 deletions src/macos/impl_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use objc2_core_graphics::{
CGDisplayBounds, CGMainDisplayID, CGRectContainsPoint, CGRectIntersectsRect,
CGRectMakeWithDictionaryRepresentation, CGWindowListCopyWindowInfo, CGWindowListOption,
};
use objc2_foundation::{NSNumber, NSString};

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

Expand All @@ -30,7 +31,6 @@ pub(crate) struct ImplWindow {
pub height: u32,
pub is_minimized: bool,
pub is_maximized: bool,
pub is_focused: bool,
}

unsafe impl Send for ImplWindow {}
Expand Down Expand Up @@ -117,7 +117,6 @@ impl ImplWindow {
window_name: String,
window_owner_name: String,
z: i32,
focused_app_pid: Option<i32>,
) -> XCapResult<ImplWindow> {
let id = get_cf_number_i32_value(window_cf_dictionary, "kCGWindowNumber")? as u32;
let pid = get_cf_number_i32_value(window_cf_dictionary, "kCGWindowOwnerPID")?;
Expand Down Expand Up @@ -154,8 +153,6 @@ impl ImplWindow {
let is_minimized =
!get_cf_bool_value(window_cf_dictionary, "kCGWindowIsOnscreen")? && !is_maximized;

let is_focused = focused_app_pid.eq(&Some(pid));

Ok(ImplWindow {
id,
title: window_name,
Expand All @@ -169,17 +166,26 @@ impl ImplWindow {
height: cg_rect.size.height as u32,
is_minimized,
is_maximized,
is_focused,
})
}

pub fn is_focused(&self) -> bool {
let focused_app_pid = unsafe {
let workspace = NSWorkspace::sharedWorkspace();
let pid_key = NSString::from_str("NSApplicationProcessIdentifier");
workspace
.activeApplication()
.and_then(|dictionary| dictionary.valueForKey(&pid_key))
.and_then(|pid| pid.downcast::<NSNumber>().ok())
.map(|pid| pid.intValue())
};

focused_app_pid.eq(&Some(self.pid as i32))
}

pub fn all() -> XCapResult<Vec<ImplWindow>> {
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();

Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ impl Window {
}
/// The window is focused.
pub fn is_focused(&self) -> bool {
self.impl_window.is_focused
self.impl_window.is_focused()
}
}

Expand Down
7 changes: 4 additions & 3 deletions src/windows/impl_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ 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 {
Expand Down Expand Up @@ -330,7 +329,6 @@ 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,
Expand All @@ -347,11 +345,14 @@ impl ImplWindow {
height: (rc_client.bottom - rc_client.top) as u32,
is_minimized,
is_maximized,
is_focused,
})
}
}

pub fn is_focused(&self) -> bool {
unsafe { GetForegroundWindow() == self.hwnd }
}

pub fn all() -> XCapResult<Vec<ImplWindow>> {
// (HWND, i32) 表示当前窗口以及层级,既(窗口,层级 z),i32 表示 max_z_order,既最大的窗口的 z 顺序
// 窗口当前层级为 max_z_order - z
Expand Down

0 comments on commit 214cb44

Please sign in to comment.