From b0ee6db9fa9901c675b3c7e952c2a8ce987a0f58 Mon Sep 17 00:00:00 2001 From: Shawn Wallace Date: Tue, 29 Oct 2024 23:24:44 -0400 Subject: [PATCH] Unfocus window on X11 side when keyboard focus is lost Closes #69 (nice) --- src/server/event.rs | 30 ++++++++++-------- src/server/mod.rs | 7 +++++ src/xstate/mod.rs | 28 ++++++++--------- tests/integration.rs | 72 +++++++++++++++++++++++++++++++++++--------- 4 files changed, 95 insertions(+), 42 deletions(-) diff --git a/src/server/event.rs b/src/server/event.rs index 2635d3a..79f4779 100644 --- a/src/server/event.rs +++ b/src/server/event.rs @@ -525,6 +525,24 @@ impl HandleEvent for Keyboard { self.server.enter(serial, &data.server, keys); } } + client::wl_keyboard::Event::Leave { serial, surface } => { + if !surface.is_alive() { + return; + } + let key: ObjectKey = surface.data().copied().unwrap(); + if let Some(data) = state + .objects + .get(key) + .map(|o| <_ as AsRef>::as_ref(o)) + { + if state.to_focus == Some(data.window.unwrap()) { + state.to_focus.take(); + } else { + state.unfocus = true; + } + self.server.leave(serial, &data.server); + } + } _ => simple_event_shunt! { self.server, event: client::wl_keyboard::Event => [ Keymap { @@ -532,18 +550,6 @@ impl HandleEvent for Keyboard { |fd| fd.as_fd(), size }, - Leave { - serial, - |surface| { - if !surface.is_alive() { - return; - } - let Some(surface_data) = state.get_server_surface_from_client(surface) else { - return; - }; - surface_data - } - }, Key { serial, time, diff --git a/src/server/mod.rs b/src/server/mod.rs index f23cd95..ada47c9 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -479,6 +479,7 @@ pub struct ServerState { qh: ClientQueueHandle, client: Option, to_focus: Option, + unfocus: bool, last_focused_toplevel: Option, last_hovered: Option, connection: Option, @@ -527,6 +528,7 @@ impl ServerState { qh, dh, to_focus: None, + unfocus: false, last_focused_toplevel: None, last_hovered: None, connection: None, @@ -832,7 +834,12 @@ impl ServerState { debug!("focusing window {win:?}"); conn.focus_window(win, data); self.last_focused_toplevel = Some(win); + } else if self.unfocus { + let data = C::ExtraData::create(self); + let conn = self.connection.as_mut().unwrap(); + conn.focus_window(x::WINDOW_NONE, data); } + self.unfocus = false; } self.handle_clipboard_events(); diff --git a/src/xstate/mod.rs b/src/xstate/mod.rs index 0d6ae45..8f40ce9 100644 --- a/src/xstate/mod.rs +++ b/src/xstate/mod.rs @@ -294,13 +294,6 @@ impl XState { xcb::Event::X(x::Event::UnmapNotify(e)) => { trace!("unmap event: {:?}", e.event()); server_state.unmap_window(e.window()); - unwrap_or_skip_bad_window_cont!(self.connection.send_and_check_request( - &x::ChangeWindowAttributes { - window: e.window(), - value_list: &[x::Cw::EventMask(x::EventMask::empty())], - } - )); - let active_win = self .connection .wait_for_reply(self.get_property_cookie( @@ -313,16 +306,19 @@ impl XState { let active_win: &[x::Window] = active_win.value(); if active_win[0] == e.window() { - self.connection - .send_and_check_request(&x::ChangeProperty { - mode: x::PropMode::Replace, - window: self.root, - property: self.atoms.active_win, - r#type: x::ATOM_WINDOW, - data: &[x::Window::none()], - }) - .unwrap(); + <_ as super::XConnection>::focus_window( + &mut self.connection, + x::Window::none(), + self.atoms.clone(), + ); } + + unwrap_or_skip_bad_window_cont!(self.connection.send_and_check_request( + &x::ChangeWindowAttributes { + window: e.window(), + value_list: &[x::Cw::EventMask(x::EventMask::empty())], + } + )); } xcb::Event::X(x::Event::DestroyNotify(e)) => { debug!("destroying window {:?}", e.window()); diff --git a/tests/integration.rs b/tests/integration.rs index a114e7b..37723e8 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -234,6 +234,7 @@ impl Fixture { xcb::atoms_struct! { struct Atoms { wm_protocols => b"WM_PROTOCOLS", + net_active_window => b"_NET_ACTIVE_WINDOW", wm_delete_window => b"WM_DELETE_WINDOW", clipboard => b"CLIPBOARD", targets => b"TARGETS", @@ -489,22 +490,65 @@ fn input_focus() { let mut f = Fixture::new(); let mut connection = Connection::new(&f.display); - let win = connection.new_window(connection.root, 0, 0, 20, 20, false); - connection.map_window(win); + let conn = std::cell::RefCell::new(&mut connection); + let check_focus = |win: x::Window| { + let connection = conn.borrow(); + let focus = connection + .wait_for_reply(connection.send_request(&x::GetInputFocus {})) + .unwrap() + .focus(); + assert_eq!(win, focus); + + let reply = connection + .wait_for_reply(connection.send_request(&x::GetProperty { + delete: false, + window: connection.root, + property: connection.atoms.net_active_window, + r#type: x::ATOM_WINDOW, + long_offset: 0, + long_length: 1, + })) + .unwrap(); + + assert_eq!(&[win], reply.value::()); + }; + + let mut create_win = || { + let mut connection = conn.borrow_mut(); + let win = connection.new_window(connection.root, 0, 0, 20, 20, false); + connection.map_window(win); + f.wait_and_dispatch(); + let surface = f + .testwl + .last_created_surface_id() + .expect("No surface created!"); + f.configure_and_verify_new_toplevel(&mut connection, win, surface); + (win, surface) + }; + + let (win1, surface1) = create_win(); + check_focus(win1); + let (win2, surface2) = create_win(); + check_focus(win2); + + f.testwl.focus_toplevel(surface1); + // Seems the event doesn't get caught by wait_and_dispatch... + std::thread::sleep(std::time::Duration::from_millis(10)); + check_focus(win1); + + f.testwl.unfocus_toplevel(); + std::thread::sleep(std::time::Duration::from_millis(10)); + check_focus(x::WINDOW_NONE); + + f.testwl.focus_toplevel(surface2); + std::thread::sleep(std::time::Duration::from_millis(10)); + check_focus(win2); + + conn.borrow().destroy_window(win2); f.wait_and_dispatch(); - let surface = f - .testwl - .last_created_surface_id() - .expect("No surface created!"); - f.configure_and_verify_new_toplevel(&mut connection, win, surface); + check_focus(x::WINDOW_NONE); - let focus = connection - .wait_for_reply(connection.send_request(&x::GetInputFocus {})) - .unwrap() - .focus(); - assert_eq!(win, focus); - - f.wm_delete_window(&mut connection, win, surface); + f.wm_delete_window(&mut connection, win1, surface1); } #[test]