psychon / x11rb

X11 bindings for the rust programming language, similar to xcb being the X11 C bindings
Apache License 2.0
372 stars 41 forks source link

[help]How to listen for "global" events #894

Closed yyy33 closed 1 year ago

yyy33 commented 1 year ago

Hi I m writing a program and I hope whenever there is a window getting focus I can get the title and class of that window and perform some action.

The code below is what I wrote according to the prompts from the code from chatgpt and the sample folder it can run without error but when I change the window focus it doesn t have any output.

Please help me, thank you


use x11rb::connection::Connection;
use x11rb::protocol::xproto::*;
use std::error::Error;
use x11rb::protocol::Event;

fn main() {
    example7().unwrap();
}

fn example7() -> Result<(), Box<dyn Error>> {
    // Open the connection to the X server. Use the DISPLAY environment variable.
    let (conn, screen_num) = x11rb::connect(None)?;

    // Get the screen #screen_num
    let screen = &conn.setup().roots[screen_num];

    let vau = ChangeWindowAttributesAux::default().event_mask(EventMask::FOCUS_CHANGE);
    conn.change_window_attributes(screen.root, &vau,).unwrap();

    loop {
        let event = conn.wait_for_event()?;
        match event {
            Event::FocusIn(event) => {
                println!("{:?}", event);
            }
            _ => {
                // Unknown event type, ignore it
                println!("Unknown event: {:?}", event);
            }
        }
    }
}
psychon commented 1 year ago

Focus change events are not (always) sent to the root window. You need help from the window manager. EWMH specifies that the property _NET_ACTIVE_WINDOW on the root window is updated when the focus changes.

Tipp for experimenting with X11 events: Run xev -root (or just xev if you want to know about events sent to a "normal" window).

whenever there is a window getting focus I can get the title and class of that window and perform some action.

I'll try to hack something up.

psychon commented 1 year ago
Here is the program that I came up with ```rust use std::error::Error; use x11rb::connection::Connection; use x11rb::properties::WmClass; use x11rb::protocol::xproto::*; use x11rb::protocol::Event; x11rb::atom_manager! { Atoms: AtomsCookie { _NET_ACTIVE_WINDOW, _NET_WM_NAME, UTF8_STRING, } } fn check_focus(conn: &impl Connection, atoms: &Atoms) { fn check_focus_impl(conn: &impl Connection, atoms: &Atoms) -> Result<(), Box> { let focus = conn.get_input_focus()?.reply()?.focus; let wm_class = WmClass::get(conn, focus)?; let name = conn.get_property( false, focus, atoms._NET_WM_NAME, atoms.UTF8_STRING, 0, 0x1000, )?; let name = String::from_utf8(name.reply()?.value)?; let wm_class = wm_class.reply()?; let class = std::str::from_utf8(wm_class.class())?; println!("Focus is at window 0x{focus:x} which has class {class:?} and name {name:?}"); Ok(()) } if let Err(e) = check_focus_impl(conn, atoms) { println!("Ignoring error {e:?} while checking the focus; this might just be a random race (e.g. window got the focus and was immediately closed, so we are looking at a non-existing window"); } } fn main() -> Result<(), Box> { let (conn, screen_num) = x11rb::connect(None)?; let screen = &conn.setup().roots[screen_num]; let atoms = Atoms::new(&conn)?.reply()?; let vau = ChangeWindowAttributesAux::default().event_mask(EventMask::PROPERTY_CHANGE); conn.change_window_attributes(screen.root, &vau)?; // Without this, the change_window_attributes() is not actually sent to the X11 server conn.flush()?; check_focus(&conn, &atoms); loop { match conn.wait_for_event()? { Event::PropertyNotify(event) if event.atom == atoms._NET_ACTIVE_WINDOW => { check_focus(&conn, &atoms); } _ => {} } } } ```

_NET_WM_NAME is also from EWMH. This code uses from_utf8 on the class of the window, even though ICCCM specifies that this is encoded in Latin1. But "usually" everyone uses ASCII for this, anyway.

Also, for laziness, I just used get_input_focus() to get the right window. I am not actually sure that this is correct. It might be necessary to actually use the value of _NET_ACTIVE_WINDOW since the input focus could be given to a child window of this window. However, for now I don't care enough to actually do that. (Welcome to X11 where everything is complicated...).

Edit: Well... okay. To actually read _NET_ACTIVE_WINDOW, replace let focus = conn.get_input_focus()?.reply()?.focus; with:

let focus = conn
    .get_property(
        false,
        root,
        atoms._NET_ACTIVE_WINDOW,
        AtomEnum::WINDOW,
        0,
        1,
    )?
    .reply()?
    .value32()
    .ok_or("_NET_ACTIVE_WINDOW has incorrect format")?
    .next()
    .ok_or("_NET_ACTIVE_WINDOW is empty")?;
yyy33 commented 1 year ago

Focus change events are not (always) sent to the root window. You need help from the window manager. EWMH specifies that the property _NET_ACTIVE_WINDOW on the root window is updated when the focus changes.

Tipp for experimenting with X11 events: Run xev -root (or just xev if you want to know about events sent to a "normal" window).

Thank you so much, I managed to run the code you gave, to the desired effect.

Welcome to X11 where everything is complicated...

Lol, it is a complicated x11, and I spent a day searching around and checking the official documents to solve the problem.