Fix hover when focus is different from hover window

Fixes #14
This commit is contained in:
Shawn Wallace 2024-06-30 02:44:55 -04:00
parent d3a46b7c8a
commit d8a9c28fa7
5 changed files with 94 additions and 17 deletions

View file

@ -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<C: XConnection> {

View file

@ -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() {

View file

@ -427,6 +427,7 @@ pub struct ServerState<C: XConnection> {
qh: ClientQueueHandle,
to_focus: Option<x::Window>,
last_focused_toplevel: Option<x::Window>,
last_hovered: Option<x::Window>,
connection: Option<C>,
xdg_wm_base: XdgWmBase,
@ -468,6 +469,7 @@ impl<C: XConnection> ServerState<C> {
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<C: XConnection> ServerState<C> {
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<C: XConnection> ServerState<C> {
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<C: XConnection> ServerState<C> {
if self.last_focused_toplevel == Some(window) {
self.last_focused_toplevel.take();
}
if self.last_hovered == Some(window) {
self.last_hovered.take();
}
}
}

View file

@ -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<FakeXConnection>;
@ -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::<WlPointer>::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::<WlPointer>::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() {}

View file

@ -842,10 +842,6 @@ impl super::XConnection for Arc<xcb::Connection> {
});
}
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<xcb::Connection> {
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<Arc<xcb::Connection>> for Atoms {