From d8a9c28fa7b0539569b8ef36af2c34bc9174db6d Mon Sep 17 00:00:00 2001 From: Shawn Wallace Date: Sun, 30 Jun 2024 02:44:55 -0400 Subject: [PATCH] Fix hover when focus is different from hover window Fixes #14 --- src/lib.rs | 1 + src/server/event.rs | 26 ++++++++++++-------- src/server/mod.rs | 15 +++++++++--- src/server/tests.rs | 58 +++++++++++++++++++++++++++++++++++++++++++++ src/xstate/mod.rs | 11 +++++---- 5 files changed, 94 insertions(+), 17 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 45ddcdf..e9f7d8b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,6 +24,7 @@ pub trait XConnection: Sized + 'static { fn set_fullscreen(&mut self, window: x::Window, fullscreen: bool, data: Self::ExtraData); fn focus_window(&mut self, window: x::Window, data: Self::ExtraData); fn close_window(&mut self, window: x::Window, data: Self::ExtraData); + fn raise_to_top(&mut self, window: x::Window); } pub trait FromServerState { diff --git a/src/server/event.rs b/src/server/event.rs index d028494..934ea52 100644 --- a/src/server/event.rs +++ b/src/server/event.rs @@ -385,17 +385,23 @@ impl HandleEvent for Pointer { ref surface, surface_x, surface_y, - } => { - let do_enter = || { - debug!("entering surface ({serial})"); - if let Some(surface) = state.get_server_surface_from_client(surface.clone()) { - self.server.enter(serial, surface, surface_x, surface_y) - } else { - warn!("could not enter surface: stale surface"); - } - }; + } => 'enter: { let surface_key: ObjectKey = surface.data().copied().unwrap(); - let surface_data: &SurfaceData = state.objects[surface_key].as_ref(); + let Some(surface_data): Option<&SurfaceData> = + state.objects.get(surface_key).map(|o| o.as_ref()) + else { + warn!("could not enter surface: stale surface"); + break 'enter; + }; + + let mut do_enter = || { + debug!("entering surface ({serial})"); + self.server + .enter(serial, &surface_data.server, surface_x, surface_y); + let window = surface_data.window.unwrap(); + state.connection.as_mut().unwrap().raise_to_top(window); + state.last_hovered = Some(window); + }; if matches!(surface_data.role, Some(SurfaceRole::Popup(_))) { match self.pending_enter.0.take() { diff --git a/src/server/mod.rs b/src/server/mod.rs index 9250e99..2565836 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -427,6 +427,7 @@ pub struct ServerState { qh: ClientQueueHandle, to_focus: Option, last_focused_toplevel: Option, + last_hovered: Option, connection: Option, xdg_wm_base: XdgWmBase, @@ -468,6 +469,7 @@ impl ServerState { dh, to_focus: None, last_focused_toplevel: None, + last_hovered: None, connection: None, objects: Default::default(), associated_windows: Default::default(), @@ -629,6 +631,9 @@ impl ServerState { if matches!(self.last_focused_toplevel, Some(x) if x == window) { self.last_focused_toplevel.take(); } + if self.last_hovered == Some(window) { + self.last_hovered.take(); + } win.mapped = false; if let Some(key) = win.surface_key.take() { @@ -792,9 +797,10 @@ impl ServerState { let window_data = self.windows.get_mut(&window).unwrap(); if window_data.attrs.override_redirect { // Override redirect is hard to convert to Wayland! - // We will just make them be popups for the last focused toplevel. - if let Some(win) = self.last_focused_toplevel { - window_data.attrs.popup_for = Some(win) + if let Some(win) = self.last_hovered { + window_data.attrs.popup_for = Some(win); + } else if let Some(win) = self.last_focused_toplevel { + window_data.attrs.popup_for = Some(win); } } let window = self.windows.get(&window).unwrap(); @@ -932,6 +938,9 @@ impl ServerState { if self.last_focused_toplevel == Some(window) { self.last_focused_toplevel.take(); } + if self.last_hovered == Some(window) { + self.last_hovered.take(); + } } } diff --git a/src/server/tests.rs b/src/server/tests.rs index ca38476..52fb17f 100644 --- a/src/server/tests.rs +++ b/src/server/tests.rs @@ -13,6 +13,7 @@ use wayland_client::{ wl_compositor::WlCompositor, wl_display::WlDisplay, wl_keyboard::WlKeyboard, + wl_pointer::WlPointer, wl_registry::WlRegistry, wl_seat::{self, WlSeat}, wl_shm::{Format, WlShm}, @@ -206,6 +207,13 @@ impl super::XConnection for FakeXConnection { ); self.focused_window = window.into(); } + + fn raise_to_top(&mut self, window: Window) { + assert!( + self.windows.contains_key(&window), + "Unknown window: {window:?}" + ); + } } type FakeServerState = ServerState; @@ -1120,6 +1128,56 @@ fn clipboard_x11_then_wayland() { } } +#[test] +fn raise_window_on_pointer_event() { + let (mut f, comp) = TestFixture::new_with_compositor(); + TestObject::::from_request(&comp.seat.obj, wl_seat::Request::GetPointer {}); + let win1 = unsafe { Window::new(1) }; + let (_, id1) = f.create_toplevel(&comp, win1); + f.testwl.configure_toplevel(id1, 100, 100, vec![]); + + let win2 = unsafe { Window::new(2) }; + let (_, id2) = f.create_toplevel(&comp, win2); + assert_eq!(f.connection().focused_window, Some(win2)); + + f.testwl.move_pointer_to(id2, 0.0, 0.0); + f.run(); + assert_eq!(f.connection().focused_window, Some(win2)); + assert_eq!(f.exwayland.last_hovered, Some(win2)); + + f.testwl.move_pointer_to(id1, 0.0, 0.0); + f.run(); + assert_eq!(f.connection().focused_window, Some(win2)); + assert_eq!(f.exwayland.last_hovered, Some(win1)); +} + +#[test] +fn override_redirect_choose_hover_window() { + let (mut f, comp) = TestFixture::new_with_compositor(); + TestObject::::from_request(&comp.seat.obj, wl_seat::Request::GetPointer {}); + let win1 = unsafe { Window::new(1) }; + let (_, id1) = f.create_toplevel(&comp, win1); + f.testwl.configure_toplevel(id1, 100, 100, vec![]); + + let win2 = unsafe { Window::new(2) }; + let _ = f.create_toplevel(&comp, win2); + assert_eq!(f.connection().focused_window, Some(win2)); + + f.testwl.move_pointer_to(id1, 0.0, 0.0); + f.run(); + assert_eq!(f.exwayland.last_hovered, Some(win1)); + + let win3 = unsafe { Window::new(3) }; + let (buffer, surface) = comp.create_surface(); + f.new_window(win3, true, WindowData::default(), None); + f.map_window(&comp, win3, &surface.obj, &buffer); + f.run(); + let id3 = f.check_new_surface(); + let popup_data = f.testwl.get_surface_data(id3).unwrap(); + let win1_xdg = &f.testwl.get_surface_data(id1).unwrap().xdg().surface; + assert_eq!(&popup_data.popup().parent, win1_xdg); +} + /// See Pointer::handle_event for an explanation. #[test] fn popup_pointer_motion_workaround() {} diff --git a/src/xstate/mod.rs b/src/xstate/mod.rs index f5b76ed..d60a757 100644 --- a/src/xstate/mod.rs +++ b/src/xstate/mod.rs @@ -842,10 +842,6 @@ impl super::XConnection for Arc { }); } - unwrap_or_skip_bad_window!(self.send_and_check_request(&x::ConfigureWindow { - window, - value_list: &[x::ConfigWindow::StackMode(x::StackMode::Above)], - })); unwrap_or_skip_bad_window!(self.send_and_check_request(&x::ChangeProperty { mode: x::PropMode::Replace, window: self.root_window(), @@ -870,6 +866,13 @@ impl super::XConnection for Arc { event, })); } + + fn raise_to_top(&mut self, window: x::Window) { + unwrap_or_skip_bad_window!(self.send_and_check_request(&x::ConfigureWindow { + window, + value_list: &[x::ConfigWindow::StackMode(x::StackMode::Above)], + })); + } } impl super::FromServerState> for Atoms {