fix: do not report unfocused if focus moved to surface's own popup
This commit is contained in:
parent
87e1787fbb
commit
5fc4372d87
3 changed files with 103 additions and 5 deletions
|
|
@ -412,6 +412,7 @@ impl SctkEventLoop {
|
||||||
lock_surfaces: Vec::new(),
|
lock_surfaces: Vec::new(),
|
||||||
subsurfaces: Vec::new(),
|
subsurfaces: Vec::new(),
|
||||||
touch_points: HashMap::new(),
|
touch_points: HashMap::new(),
|
||||||
|
kbd_leave_to_own_popup: HashSet::new(),
|
||||||
sctk_events: Vec::new(),
|
sctk_events: Vec::new(),
|
||||||
frame_status: HashMap::new(),
|
frame_status: HashMap::new(),
|
||||||
cursor_requests: HashMap::new(),
|
cursor_requests: HashMap::new(),
|
||||||
|
|
|
||||||
|
|
@ -435,6 +435,9 @@ pub struct SctkState {
|
||||||
pub(crate) blur_surfaces: HashMap<core::window::Id, ExtBackgroundEffectSurfaceV1>,
|
pub(crate) blur_surfaces: HashMap<core::window::Id, ExtBackgroundEffectSurfaceV1>,
|
||||||
pub(crate) corner_radii: HashMap<core::window::Id, (SctkCornerRadius, Option<CornerRadius>)>,
|
pub(crate) corner_radii: HashMap<core::window::Id, (SctkCornerRadius, Option<CornerRadius>)>,
|
||||||
pub(crate) touch_points: HashMap<touch::Finger, (WlSurface, Point)>,
|
pub(crate) touch_points: HashMap<touch::Finger, (WlSurface, Point)>,
|
||||||
|
/// List of layer surfaces whose keyboard leave was ignored because focus moved
|
||||||
|
/// to one of their own grabbing popups. Their next enter is swallowed too.
|
||||||
|
pub(crate) kbd_leave_to_own_popup: HashSet<ObjectId>,
|
||||||
|
|
||||||
/// Window updates, which are coming from SCTK or the compositor, which require
|
/// Window updates, which are coming from SCTK or the compositor, which require
|
||||||
/// calling back to the sctk's downstream. They are handled right in the event loop,
|
/// calling back to the sctk's downstream. They are handled right in the event loop,
|
||||||
|
|
@ -681,6 +684,33 @@ impl SctkState {
|
||||||
// TODO winit sets cursor size after handling the change for the window, so maybe that should be done as well.
|
// TODO winit sets cursor size after handling the change for the window, so maybe that should be done as well.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Popups can only be attached to xdg or layer surfaces. So for subsurfaces, we return the
|
||||||
|
/// parent, and adjust the offset.
|
||||||
|
pub(crate) fn subsurface_popup_parent(
|
||||||
|
&self,
|
||||||
|
id: core::window::Id,
|
||||||
|
) -> Option<(WlSurface, (i32, i32))> {
|
||||||
|
let mut sub = self.subsurfaces.iter().find(|s| s.id == id)?;
|
||||||
|
let mut offset = (0, 0);
|
||||||
|
// bounded by the number of subsurfaces so a corrupt chain cannot spin forever
|
||||||
|
for _ in 0..=self.subsurfaces.len() {
|
||||||
|
if let Some(b) = sub.instance.bounds {
|
||||||
|
offset.0 += b.x as i32;
|
||||||
|
offset.1 += b.y as i32;
|
||||||
|
}
|
||||||
|
let parent = &sub.instance.parent;
|
||||||
|
match self
|
||||||
|
.subsurfaces
|
||||||
|
.iter()
|
||||||
|
.find(|s| s.instance.wl_surface == *parent)
|
||||||
|
{
|
||||||
|
Some(s) => sub = s,
|
||||||
|
None => return Some((parent.clone(), offset)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_popup(
|
pub fn get_popup(
|
||||||
&mut self,
|
&mut self,
|
||||||
settings: SctkPopupSettings,
|
settings: SctkPopupSettings,
|
||||||
|
|
@ -725,6 +755,27 @@ impl SctkState {
|
||||||
PopupParent::Popup(parent.popup.wl_surface().clone()),
|
PopupParent::Popup(parent.popup.wl_surface().clone()),
|
||||||
parent.data.toplevel.clone(),
|
parent.data.toplevel.clone(),
|
||||||
)
|
)
|
||||||
|
} else if let Some((surface, _)) =
|
||||||
|
self.subsurface_popup_parent(settings.parent)
|
||||||
|
{
|
||||||
|
if self
|
||||||
|
.layer_surfaces
|
||||||
|
.iter()
|
||||||
|
.any(|l| *l.surface.wl_surface() == surface)
|
||||||
|
{
|
||||||
|
(PopupParent::LayerSurface(surface.clone()), surface)
|
||||||
|
} else if self
|
||||||
|
.windows
|
||||||
|
.iter()
|
||||||
|
.any(|w| w.wl_surface(&self.connection) == surface)
|
||||||
|
{
|
||||||
|
(PopupParent::Window(surface.clone()), surface)
|
||||||
|
} else if let Some(p) = self.popmgr.popup(&surface) {
|
||||||
|
(PopupParent::Popup(surface), p.data.toplevel.clone())
|
||||||
|
} else {
|
||||||
|
// e.g. a session lock surface, which cannot parent xdg popups
|
||||||
|
return Err(PopupCreationError::ParentMissing);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(PopupCreationError::ParentMissing);
|
return Err(PopupCreationError::ParentMissing);
|
||||||
};
|
};
|
||||||
|
|
@ -1158,6 +1209,7 @@ impl SctkState {
|
||||||
}
|
}
|
||||||
if let Some(i) = self.layer_surfaces.iter().position(|l| l.id == id) {
|
if let Some(i) = self.layer_surfaces.iter().position(|l| l.id == id) {
|
||||||
let l = self.layer_surfaces.remove(i);
|
let l = self.layer_surfaces.remove(i);
|
||||||
|
_ = self.kbd_leave_to_own_popup.remove(&l.surface.wl_surface().id());
|
||||||
|
|
||||||
if let Some(blurred) = self.blur_surfaces.remove(&l.id) {
|
if let Some(blurred) = self.blur_surfaces.remove(&l.id) {
|
||||||
blurred.destroy();
|
blurred.destroy();
|
||||||
|
|
@ -1285,13 +1337,19 @@ impl SctkState {
|
||||||
},
|
},
|
||||||
Action::Popup(action) => {
|
Action::Popup(action) => {
|
||||||
match action {
|
match action {
|
||||||
platform_specific::wayland::popup::Action::Popup { popup: settings } => {
|
platform_specific::wayland::popup::Action::Popup { popup: mut settings } => {
|
||||||
|
// anchor rects from widgets inside a subsurface are in that subsurface's coordinates
|
||||||
|
if let Some((_, (dx, dy))) = self.subsurface_popup_parent(settings.parent) {
|
||||||
|
settings.positioner.anchor_rect.x += dx;
|
||||||
|
settings.positioner.anchor_rect.y += dy;
|
||||||
|
}
|
||||||
// first check existing popup
|
// first check existing popup
|
||||||
// if an existing popup with the same id exists, we just update the positioner.
|
// if an existing popup with the same id exists, we just update the positioner.
|
||||||
if let Some((chain, existing)) = self.popmgr.popup_id(settings.id).and_then(|p|
|
if let Some((chain, existing)) = self.popmgr.popup_id(settings.id).and_then(|p|
|
||||||
(p.data.grab == settings.grab && self.popmgr.popups().any(|parent| parent.popup.wl_surface() == p.data.parent.wl_surface() && parent.data.id == settings.parent)
|
(p.data.grab == settings.grab && self.popmgr.popups().any(|parent| parent.popup.wl_surface() == p.data.parent.wl_surface() && parent.data.id == settings.parent)
|
||||||
|| self.windows.iter().any(|w| w.id == settings.parent && *p.data.parent.wl_surface() == w.wl_surface(&self.connection))
|
|| self.windows.iter().any(|w| w.id == settings.parent && *p.data.parent.wl_surface() == w.wl_surface(&self.connection))
|
||||||
|| self.layer_surfaces.iter().any(|l| l.id == settings.parent && p.data.parent.wl_surface() == l.surface.wl_surface())).then(||
|
|| self.layer_surfaces.iter().any(|l| l.id == settings.parent && p.data.parent.wl_surface() == l.surface.wl_surface())
|
||||||
|
|| (p.data.parent_window == settings.parent && self.subsurfaces.iter().any(|s| s.id == settings.parent))).then(||
|
||||||
{
|
{
|
||||||
p.popup.wl_surface().clone()
|
p.popup.wl_surface().clone()
|
||||||
})).and_then(|s| self.popmgr.chain_for_popup_mut(&s))
|
})).and_then(|s| self.popmgr.chain_for_popup_mut(&s))
|
||||||
|
|
@ -1662,9 +1720,13 @@ impl SctkState {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
subsurface::Action::Reposition { id, x, y } => {
|
subsurface::Action::Reposition { id, x, y } => {
|
||||||
if let Some(subsurface) = self.subsurfaces.iter().find(|s| s.id == id) {
|
if let Some(subsurface) = self.subsurfaces.iter_mut().find(|s| s.id == id) {
|
||||||
subsurface.instance.wl_subsurface.set_position(x, y);
|
subsurface.instance.wl_subsurface.set_position(x, y);
|
||||||
subsurface.instance.wl_surface.commit();
|
subsurface.instance.wl_surface.commit();
|
||||||
|
if let Some(b) = subsurface.instance.bounds.as_mut() {
|
||||||
|
b.x = x as f32;
|
||||||
|
b.y = y as f32;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
use crate::platform_specific::wayland::{
|
use crate::platform_specific::wayland::{
|
||||||
event_loop::state::SctkState,
|
event_loop::state::{PopupParent, SctkState},
|
||||||
sctk_event::{KeyboardEventVariant, SctkEvent},
|
sctk_event::{KeyboardEventVariant, SctkEvent},
|
||||||
};
|
};
|
||||||
use cctk::sctk::{
|
use cctk::sctk::{
|
||||||
delegate_keyboard,
|
delegate_keyboard,
|
||||||
seat::keyboard::{KeyboardHandler, Keysym, Modifiers},
|
seat::keyboard::{KeyboardHandler, Keysym, Modifiers},
|
||||||
};
|
};
|
||||||
use cctk::sctk::{reexports::client::Proxy, seat::keyboard::RawModifiers};
|
use cctk::sctk::{
|
||||||
|
reexports::client::Proxy,
|
||||||
|
seat::keyboard::RawModifiers,
|
||||||
|
shell::{WaylandSurface, wlr_layer::KeyboardInteractivity},
|
||||||
|
};
|
||||||
|
|
||||||
fn modifiers_from_keysyms(
|
fn modifiers_from_keysyms(
|
||||||
keysyms: &[Keysym],
|
keysyms: &[Keysym],
|
||||||
|
|
@ -90,6 +94,11 @@ impl KeyboardHandler for SctkState {
|
||||||
}
|
}
|
||||||
self.request_redraw(&surface);
|
self.request_redraw(&surface);
|
||||||
|
|
||||||
|
// Focus is coming back from our own grabbing popup
|
||||||
|
if self.kbd_leave_to_own_popup.remove(&surface.id()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let surfaces = self.subsurfaces.iter().filter_map(|s| {
|
let surfaces = self.subsurfaces.iter().filter_map(|s| {
|
||||||
(s.instance.parent == *surface).then(|| &s.instance.wl_surface)
|
(s.instance.parent == *surface).then(|| &s.instance.wl_surface)
|
||||||
});
|
});
|
||||||
|
|
@ -147,6 +156,32 @@ impl KeyboardHandler for SctkState {
|
||||||
_ = my_seat.kbd_focus.take();
|
_ = my_seat.kbd_focus.take();
|
||||||
(is_active, seat, kbd)
|
(is_active, seat, kbd)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A grabbing popup parented to this layer surface, or opened from one of
|
||||||
|
// its subsurfaces that holds focus, takes keyboard focus.
|
||||||
|
// Only swallow the leave when the layer surface has exclusive keyboard interactivity.
|
||||||
|
let focus_moved_to_own_popup = self.popmgr.popups().any(|p| {
|
||||||
|
p.data.grab
|
||||||
|
&& match &p.data.parent {
|
||||||
|
PopupParent::LayerSurface(s) => {
|
||||||
|
(s == surface
|
||||||
|
|| self.subsurfaces.iter().any(|sub| {
|
||||||
|
sub.id == p.data.parent_window
|
||||||
|
&& sub.instance.wl_surface == *surface
|
||||||
|
}))
|
||||||
|
&& self.layer_surfaces.iter().any(|l| {
|
||||||
|
l.surface.wl_surface() == s
|
||||||
|
&& l.keyboard_interactivity
|
||||||
|
== KeyboardInteractivity::Exclusive
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if focus_moved_to_own_popup {
|
||||||
|
_ = self.kbd_leave_to_own_popup.insert(surface.id());
|
||||||
|
return;
|
||||||
|
}
|
||||||
let surfaces = self.subsurfaces.iter().filter_map(|s| {
|
let surfaces = self.subsurfaces.iter().filter_map(|s| {
|
||||||
(s.instance.parent == *surface).then(|| &s.instance.wl_surface)
|
(s.instance.parent == *surface).then(|| &s.instance.wl_surface)
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue