Improve active window determination
This commit is contained in:
parent
65e405232d
commit
8212f4d89c
1
.gitignore
vendored
1
.gitignore
vendored
@ -21,6 +21,7 @@ Cargo.lock
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
/test/
|
||||
|
||||
# Added by cargo
|
||||
|
||||
|
||||
180
src/main.rs
180
src/main.rs
@ -2,7 +2,7 @@ use std::error::Error;
|
||||
|
||||
use clap::Parser;
|
||||
use serde::Serialize;
|
||||
use xcb::{XidNew, x};
|
||||
use xcb::{Xid, XidNew, x};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(name = "xdi", about = "Print X11 desktop information as JSON")]
|
||||
@ -44,10 +44,16 @@ xcb::atoms_struct! {
|
||||
net_wm_name => b"_NET_WM_NAME",
|
||||
utf8_string => b"UTF8_STRING",
|
||||
wm_class => b"WM_CLASS",
|
||||
wm_state => b"WM_STATE",
|
||||
net_wm_window_type => b"_NET_WM_WINDOW_TYPE",
|
||||
net_wm_window_type_dock => b"_NET_WM_WINDOW_TYPE_DOCK",
|
||||
net_wm_window_type_desktop => b"_NET_WM_WINDOW_TYPE_DESKTOP",
|
||||
net_wm_window_type_toolbar => b"_NET_WM_WINDOW_TYPE_TOOLBAR",
|
||||
net_wm_window_type_menu => b"_NET_WM_WINDOW_TYPE_MENU",
|
||||
net_wm_window_type_utility => b"_NET_WM_WINDOW_TYPE_UTILITY",
|
||||
net_wm_state => b"_NET_WM_STATE",
|
||||
net_wm_state_above => b"_NET_WM_STATE_ABOVE",
|
||||
net_wm_state_sticky => b"_NET_WM_STATE_STICKY",
|
||||
net_wm_state_above => b"_NET_WM_STATE_ABOVE",
|
||||
wm_state => b"WM_STATE",
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,15 +93,21 @@ impl X11 {
|
||||
.copied()
|
||||
.unwrap_or(0);
|
||||
|
||||
// Try to get active window from _NET_ACTIVE_WINDOW
|
||||
let active_id = self
|
||||
.property_u32(self.root, self.atoms.net_active_window)?
|
||||
.first()
|
||||
.copied();
|
||||
|
||||
// Try to get stacking order to determine active window
|
||||
let stacking_ids = self
|
||||
.property_u32(self.root, self.atoms.net_client_list_stacking)
|
||||
.unwrap_or_default();
|
||||
// Get the window with keyboard focus as a fallback
|
||||
let focused_window = self.get_focused_window().ok().flatten();
|
||||
|
||||
// Use the focused window if _NET_ACTIVE_WINDOW is 0 or invalid
|
||||
let effective_active_id = if active_id == Some(0) || active_id.is_none() {
|
||||
focused_window
|
||||
} else {
|
||||
active_id
|
||||
};
|
||||
|
||||
let client_ids = self.property_u32(self.root, self.atoms.net_client_list)?;
|
||||
|
||||
@ -108,9 +120,13 @@ impl X11 {
|
||||
.collect();
|
||||
|
||||
let mut active_window = None;
|
||||
let mut windows_on_current = Vec::new();
|
||||
|
||||
for id in client_ids {
|
||||
// Skip dock/panel windows
|
||||
if self.is_dock_window(id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to get window info, skip if it fails (window might be invalid)
|
||||
let Ok(window) = self.window_info(id) else {
|
||||
continue;
|
||||
@ -135,59 +151,17 @@ impl X11 {
|
||||
continue;
|
||||
};
|
||||
|
||||
if desktop == current {
|
||||
windows_on_current.push(id);
|
||||
}
|
||||
|
||||
if active_id == Some(id) {
|
||||
// Check if this is the active window
|
||||
if effective_active_id == Some(id) {
|
||||
active_window = Some(window.clone());
|
||||
}
|
||||
|
||||
workspace.windows.push(window);
|
||||
}
|
||||
|
||||
// If _NET_ACTIVE_WINDOW is 0 or not set, try to determine active window
|
||||
// from the stacking order on the current workspace
|
||||
if active_window.is_none() || active_id == Some(0) {
|
||||
// Find the topmost window on the current workspace
|
||||
let mut current_workspace_windows: Vec<_> = windows_on_current
|
||||
.iter()
|
||||
.filter(|id| {
|
||||
// Check if this window is on the current workspace
|
||||
self.property_u32_opt(x::Window::new(**id), self.atoms.net_wm_desktop)
|
||||
.ok()
|
||||
.flatten()
|
||||
.and_then(|v| v.first().copied())
|
||||
== Some(current)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Sort by stacking order (if available)
|
||||
if !stacking_ids.is_empty() {
|
||||
current_workspace_windows.sort_by_key(|id| {
|
||||
stacking_ids
|
||||
.iter()
|
||||
.position(|&stack_id| stack_id == **id)
|
||||
.unwrap_or(usize::MAX)
|
||||
});
|
||||
// The last one in stacking order is on top
|
||||
if let Some(&top_id) = current_workspace_windows.last()
|
||||
&& let Ok(info) = self.window_info(*top_id)
|
||||
{
|
||||
active_window = Some(info);
|
||||
}
|
||||
} else if let Some(&top_id) = current_workspace_windows.last() {
|
||||
// If no stacking info, just use the last found
|
||||
if let Ok(info) = self.window_info(*top_id) {
|
||||
active_window = Some(info);
|
||||
}
|
||||
}
|
||||
|
||||
// If we still don't have an active window, try the old method
|
||||
if active_window.is_none()
|
||||
&& let Some(id) = active_id
|
||||
&& id != 0
|
||||
{
|
||||
// If we still don't have an active window, try to find the focused window
|
||||
if active_window.is_none() {
|
||||
if let Some(id) = effective_active_id {
|
||||
active_window = self.window_info(id).ok();
|
||||
}
|
||||
}
|
||||
@ -198,6 +172,71 @@ impl X11 {
|
||||
})
|
||||
}
|
||||
|
||||
fn get_focused_window(&self) -> Result<Option<u32>, Box<dyn Error>> {
|
||||
let cookie = self.conn.send_request(&x::GetInputFocus {});
|
||||
|
||||
let reply = self.conn.wait_for_reply(cookie)?;
|
||||
let focus = reply.focus();
|
||||
|
||||
// If focus is the root window or PointerRoot, there's no focused window
|
||||
if focus == self.root {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Check for PointerRoot (INPUTFOCUS_POINTER_ROOT is defined as Window with res_id 1)
|
||||
if focus.resource_id() == 1 {
|
||||
// Get the window under the pointer
|
||||
let cookie = self.conn.send_request(&x::QueryPointer {
|
||||
window: self.root,
|
||||
});
|
||||
let reply = self.conn.wait_for_reply(cookie)?;
|
||||
let child = reply.child();
|
||||
|
||||
if child == x::Window::new(0) {
|
||||
return Ok(None);
|
||||
}
|
||||
return Ok(Some(child.resource_id()));
|
||||
}
|
||||
|
||||
if focus == x::Window::new(0) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
Ok(Some(focus.resource_id()))
|
||||
}
|
||||
|
||||
fn is_dock_window(&self, id: u32) -> bool {
|
||||
let window = x::Window::new(id);
|
||||
|
||||
// Check if it's a dock type window
|
||||
if let Ok(Some(window_types)) = self.property_u32_opt(window, self.atoms.net_wm_window_type) {
|
||||
let dock = self.atom_to_u32(self.atoms.net_wm_window_type_dock);
|
||||
|
||||
for window_type in window_types {
|
||||
if window_type == dock {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it has sticky state (common for docks/panels)
|
||||
if let Ok(Some(states)) = self.property_u32_opt(window, self.atoms.net_wm_state) {
|
||||
let sticky = self.atom_to_u32(self.atoms.net_wm_state_sticky);
|
||||
|
||||
for state in states {
|
||||
if state == sticky {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn atom_to_u32(&self, atom: x::Atom) -> u32 {
|
||||
atom.resource_id()
|
||||
}
|
||||
|
||||
fn window_info(&self, id: u32) -> Result<WindowInfo, Box<dyn Error>> {
|
||||
let window = x::Window::new(id);
|
||||
|
||||
@ -288,7 +327,7 @@ impl X11 {
|
||||
fn watch(&self) -> Result<(), Box<dyn Error>> {
|
||||
self.select_events(
|
||||
self.root,
|
||||
x::EventMask::PROPERTY_CHANGE | x::EventMask::SUBSTRUCTURE_NOTIFY,
|
||||
x::EventMask::PROPERTY_CHANGE | x::EventMask::SUBSTRUCTURE_NOTIFY | x::EventMask::FOCUS_CHANGE,
|
||||
);
|
||||
|
||||
let client_ids = self.property_u32(self.root, self.atoms.net_client_list)?;
|
||||
@ -336,20 +375,31 @@ impl X11 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
xcb::Event::X(x::Event::FocusIn(_)) => {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
xcb::Event::X(x::Event::FocusOut(_)) => {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_relevant_property(&self, atom: x::Atom) -> bool {
|
||||
atom == self.atoms.net_number_of_desktops
|
||||
|| atom == self.atoms.net_current_desktop
|
||||
|| atom == self.atoms.net_client_list
|
||||
|| atom == self.atoms.net_client_list_stacking
|
||||
|| atom == self.atoms.net_wm_desktop
|
||||
|| atom == self.atoms.net_active_window
|
||||
|| atom == self.atoms.net_wm_name
|
||||
|| atom == self.atoms.wm_class
|
||||
let atom_u32 = self.atom_to_u32(atom);
|
||||
atom_u32 == self.atom_to_u32(self.atoms.net_number_of_desktops)
|
||||
|| atom_u32 == self.atom_to_u32(self.atoms.net_current_desktop)
|
||||
|| atom_u32 == self.atom_to_u32(self.atoms.net_client_list)
|
||||
|| atom_u32 == self.atom_to_u32(self.atoms.net_client_list_stacking)
|
||||
|| atom_u32 == self.atom_to_u32(self.atoms.net_wm_desktop)
|
||||
|| atom_u32 == self.atom_to_u32(self.atoms.net_active_window)
|
||||
|| atom_u32 == self.atom_to_u32(self.atoms.net_wm_name)
|
||||
|| atom_u32 == self.atom_to_u32(self.atoms.wm_class)
|
||||
|| atom_u32 == self.atom_to_u32(self.atoms.net_wm_window_type)
|
||||
|| atom_u32 == self.atom_to_u32(self.atoms.net_wm_state)
|
||||
}
|
||||
}
|
||||
|
||||
@ -390,4 +440,4 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
x11.wait_for_relevant_event()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user