0

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,
    }
}
user7876637
  • 124
  • 4
  • 11
  • 1
    What exactly does "cannot focus" mean? Is it "when I type text, it does not end up in the window's textfield" or is it "the window is not rendered as having focus"? Also, did you check if perhaps the window itself gives focus to something else? As in: Are you getting `FocusNotify` events for the window and then for something else? – Uli Schlachter Sep 03 '20 at 07:47
  • 1
    Also, your description of how you set the focus seems odd to me. There is no `WM_FOCUS`. If the window supports the `WM_TAKE_FOCUS` protocol, you send a `WM_TAKE_FOCUS` message to it (with a current timestamp). If the window has `input` in its `WM_HINTS`, you send a `SetInputFocus` message. These two conditions are independent. This means that you do both if both conditions are true etc. – Uli Schlachter Sep 03 '20 at 07:49
  • 1
    Finally: Could you perhaps show/link to your code? Do you have `xprop` output for the window in question? (which of `WM_TAKE_FOCUS` and the `input` field in `WM_HINTS` are "special" here?) – Uli Schlachter Sep 03 '20 at 07:50
  • Thanks so much for the help! I'm not all the way there, but this has pointed me in the right direction after my late-night coding :) 1. "Cannot focus" seems to describe the issue incorrectly; further testing shows that it's actually just that input events (button 1 press specifically) does not get sent to the window; key press events and similar actually do work as intended. 2. Right, that was a late-night typo; I did mean `WM_TAKE_FOCUS`. I was looking at how i3 works: https://github.com/i3/i3/blob/b61a28f156aad545d5c54b9a6f40ef7cae1a1c9b/src/x.c#L1286-L1337 3. I have edited in my code. – user7876637 Sep 03 '20 at 15:10
  • It turns out that my original take on this question was incorrect, though. I have edited my question to more accurately reflect what's happening. – user7876637 Sep 03 '20 at 15:10
  • I've figured it out! Thanks so much for helping (: – user7876637 Sep 03 '20 at 15:27

1 Answers1

1

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.

user7876637
  • 124
  • 4
  • 11