rustwindow-managersxcbewmhicccm

custom window manager: Some GTK+ 3 windows receive focus but will not accept mouse clicks


As the title says. I'm writing a custom X11 window manager in Rust, using the xcb library. A specific window -- the "configuration" window for cairo-dock -- will not take button 1 clicks when focused, despite ungrabbing button 1 on that window.

Previously, I thought that said window was not holding focus, but that turns out to not be correct. Instead, the window in question is receiving focus, but not allowing any button 1 clicks through.


Relevant code for setting focus:

#[allow(clippy::single_match)]
fn set_focus(&mut self, window: xproto::Window) {
    if window != self.root && window != self.focus {
        let prev = self.focus;
        // ungrab focus from the previous window
        xproto::ungrab_button(
            self.conn,
            xproto::BUTTON_INDEX_1 as u8,
            self.focus,
            0
        );
        // make sure we don't accidentally have button 1 grabbed
        xproto::ungrab_button(
            self.conn,
            xproto::BUTTON_INDEX_1 as u8,
            window,
            0
        );

        // See https://github.com/i3/i3/blob/b61a28f156aad545d5c54b9a6f40ef7cae1a1c9b/src/x.c#L1286-L1337
        if self.needs_take_focus(window)
                && self.doesnt_take_focus(window)
                && window != base::NONE
                && window != self.root {
            let client_message =
                xproto::ClientMessageEvent::new(
                    32,
                    window,
                    self.atom("WM_PROTOCOLS"),
                    xproto::ClientMessageData::from_data32(
                        [
                            self.atom("WM_TAKE_FOCUS"),
                            self.last_timestamp,
                            0,
                            0,
                            0
                        ]
                    )
                );

            xproto::send_event(self.conn, false, window, base::NONE as u32, &client_message);
        } else {
            debug!("{} can be focused normally", window);
            xproto::set_input_focus(
                self.conn,
                xproto::INPUT_FOCUS_PARENT as u8,
                window,
                self.last_timestamp,
            );
        }


        self.replace_prop(
            self.root,
            self.atom("_NET_ACTIVE_WINDOW"),
            32,
            &[window]
        );

        debug!("updating _NET_WM_STATE with _NET_WM_STATE_FOCUSED!");
        self.remove_prop(prev, self.atom("_NET_WM_STATE"), self.atom("_NET_WM_STATE_FOCUSED"));
        self.append_prop(window, self.atom("_NET_WM_STATE"), self.atom("_NET_WM_STATE_FOCUSED"));

        self.focus = window;
        debug!("focused window: {}", self.focus);
    } else if window == self.root {
        self.remove_prop(self.focus, self.atom("_NET_WM_STATE"), self.atom("_NET_WM_STATE_FOCUSED"));
        debug!("focusing root -> NONE");
        self.replace_prop(self.root, self.atom("_NET_ACTIVE_WINDOW"), 32, &[base::NONE]);
        xproto::set_input_focus(self.conn, 0, base::NONE, base::CURRENT_TIME);
        self.focus = xcb::NONE;
    }
}

fn append_prop(&self, window: xproto::Window, prop: u32, atom: u32) {
    // TODO: Check result
    xproto::change_property(
        self.conn,
        xproto::PROP_MODE_APPEND as u8,
        window,
        prop,
        xproto::ATOM_ATOM,
        32,
        &[atom]
    );
}

fn remove_prop(&self, window: xproto::Window, prop: u32, atom: u32) {
    let cookie = xproto::get_property(self.conn, false, window, prop, xproto::GET_PROPERTY_TYPE_ANY, 0, 4096);
    match cookie.get_reply() {
        Ok(res) => {
            match res.value::<u32>() {
                [] => {},
                values => {
                    let mut new_values: Vec<u32> = Vec::from(values);
                    new_values.retain(|value| value != &atom);
                    self.replace_prop(window, prop, 32, &new_values);
                },
            }
        },
        Err(err) => error!("couldn't get props to remove from: {:#?}", err),
    }
}

fn needs_take_focus(&self, window: xproto::Window) -> bool {
    let properties_cookie =
        xproto::get_property(
            self.conn,
            false,
            window,
            self.atom("WM_PROTOCOLS"),
            xproto::ATOM_ANY,
            0,
            2048
        );

    match properties_cookie.get_reply() {
        Ok(protocols) => {
            let mut needs_help = false;
            for proto in protocols.value::<u32>().iter() {
                match self.atom_by_id_checked(proto) {
                    Some("WM_TAKE_FOCUS") => {
                        needs_help = true
                    },
                    _ => (),
                }
            }
            needs_help
        },
        // FIXME
        Err(_) => false,
    }
}

fn doesnt_take_focus(&self, window: xproto::Window) -> bool {
    match xcb_util::icccm::get_wm_hints(self.conn, window).get_reply() {
        Ok(hints) => {
            if let Some(input) = hints.input() {
                input
            } else {
                false
            }
        },
        // FIXME
        Err(_) => false,
    }
}

Solution

  • It turns out my problem was that I wasn't ungrabbing button 1 correctly; focus was actually being passed correctly (see question edit history), I was just forgetting to ungrab correctly because I forgot that the initial grab had a button mask on it. Thank you so much to Uli Schlachter in the comments helping me get it figured out.