cosmic-comp/src/wayland/handlers/xdg_shell/popup.rs

482 lines
17 KiB
Rust
Raw Normal View History

2022-07-05 18:46:38 +02:00
// SPDX-License-Identifier: GPL-3.0-only
use crate::{shell::Shell, utils::prelude::*};
use smithay::{
2022-07-07 19:45:55 +02:00
desktop::{
layer_map_for_output, space::SpaceElement, LayerSurface, PopupKind, PopupManager,
WindowSurfaceType,
2022-07-07 19:45:55 +02:00
},
output::Output,
2022-07-05 18:46:38 +02:00
reexports::{
wayland_protocols::xdg::shell::server::xdg_positioner::{
Anchor, ConstraintAdjustment, Gravity,
},
wayland_server::protocol::wl_surface::WlSurface,
},
utils::{Logical, Point, Rectangle},
wayland::{
compositor::{get_role, with_states},
seat::WaylandFocus,
2022-07-05 18:46:38 +02:00
shell::xdg::{
PopupSurface, PositionerState, SurfaceCachedState, ToplevelSurface,
XdgPopupSurfaceRoleAttributes, XDG_POPUP_ROLE,
2022-07-05 18:46:38 +02:00
},
},
};
use std::sync::Mutex;
use tracing::{trace, warn};
2022-07-05 18:46:38 +02:00
impl Shell {
pub fn unconstrain_popup(&self, surface: &PopupSurface) {
2022-07-05 18:46:38 +02:00
if let Some(parent) = get_popup_toplevel(&surface) {
2023-01-18 20:23:41 +01:00
if let Some(elem) = self.element_for_wl_surface(&parent) {
let (mut element_geo, output, is_tiled) =
if let Some(workspace) = self.space_for(elem) {
(
workspace
.element_geometry(elem)
.unwrap()
.to_global(workspace.output()),
workspace.output.clone(),
workspace.is_tiled(elem),
)
} else if let Some((output, set)) = self
.workspaces
.sets
.iter()
.find(|(_, set)| set.sticky_layer.mapped().any(|m| m == elem))
{
(
set.sticky_layer
.element_geometry(elem)
.unwrap()
.to_global(output),
output.clone(),
false,
)
} else {
return;
};
2022-09-28 12:01:29 +02:00
let (window, offset) = elem
.windows()
.find(|(w, _)| w.wl_surface().as_ref() == Some(&parent))
2022-07-05 18:46:38 +02:00
.unwrap();
2022-09-28 12:01:29 +02:00
let window_geo_offset = window.geometry().loc;
let window_loc: Point<i32, Global> =
element_geo.loc + offset.as_global() + window_geo_offset.as_global();
if is_tiled {
element_geo.loc = (0, 0).into();
if !unconstrain_xdg_popup_tile(surface, element_geo.as_logical()) {
unconstrain_xdg_popup(surface, window_loc, output.geometry());
2022-12-15 13:07:38 +01:00
}
} else {
unconstrain_xdg_popup(surface, window_loc, output.geometry());
2022-09-28 12:01:29 +02:00
}
2022-07-07 19:45:55 +02:00
} else if let Some((output, layer_surface)) = self.outputs().find_map(|o| {
let map = layer_map_for_output(o);
map.layer_for_surface(&parent, WindowSurfaceType::ALL)
.map(|l| (o, l.clone()))
}) {
2022-12-15 13:07:38 +01:00
unconstrain_layer_popup(surface, output, &layer_surface)
2022-07-05 18:46:38 +02:00
}
}
}
}
2022-09-28 12:01:29 +02:00
pub fn update_reactive_popups<'a>(
toplevel: &ToplevelSurface,
loc: Point<i32, Global>,
2022-09-28 12:01:29 +02:00
outputs: impl Iterator<Item = &'a Output>,
) {
let output_geo = outputs.map(|o| o.geometry()).collect::<Vec<_>>();
for (popup, _) in PopupManager::popups_for_surface(toplevel.wl_surface()) {
2022-07-05 18:46:38 +02:00
match popup {
PopupKind::Xdg(surface) => {
let positioner = with_states(&surface.wl_surface(), |states| {
let attributes = states
.data_map
.get::<Mutex<XdgPopupSurfaceRoleAttributes>>()
.unwrap()
.lock()
.unwrap();
attributes.current.positioner.clone()
});
if positioner.reactive {
let anchor_point = loc + get_anchor_point(&positioner).as_global();
2022-09-28 12:01:29 +02:00
if let Some(rect) = output_geo
.iter()
.find(|geo| geo.contains(anchor_point))
.copied()
{
2022-12-15 13:07:38 +01:00
unconstrain_xdg_popup(&surface, loc, rect);
2022-09-28 12:01:29 +02:00
if let Err(err) = surface.send_configure() {
warn!(
?err,
"Compositor bug: Unable to re-configure reactive popup",
2022-09-28 12:01:29 +02:00
);
}
2022-07-07 19:45:55 +02:00
}
2022-07-05 18:46:38 +02:00
}
}
2023-09-29 21:33:16 +02:00
PopupKind::InputMethod(_) => {}
2022-07-05 18:46:38 +02:00
}
}
}
2022-12-15 13:07:38 +01:00
fn unconstrain_xdg_popup_tile(surface: &PopupSurface, rect: Rectangle<i32, Logical>) -> bool {
2023-10-12 12:58:30 +02:00
let toplevel_offset = get_popup_toplevel_coords(surface);
let mut geometry = surface.with_pending_state(|state| state.positioner.get_geometry());
geometry.loc += toplevel_offset;
2022-12-15 13:07:38 +01:00
let offset = check_constrained(geometry, rect);
if offset.x != 0 || offset.y != 0 {
trace!(?surface, "Unconstraining popup to tile.");
2022-12-15 13:07:38 +01:00
if !unconstrain_flip(&surface, rect) {
return unconstrain_slide(&surface, rect);
// don't try to resize for fitting to a tile
}
}
true
}
2022-07-07 19:45:55 +02:00
fn unconstrain_xdg_popup(
2022-07-05 18:46:38 +02:00
surface: &PopupSurface,
window_loc: Point<i32, Global>,
mut rect: Rectangle<i32, Global>,
2022-07-05 18:46:38 +02:00
) {
rect.loc -= window_loc;
let relative = rect.as_logical();
2023-10-12 12:58:30 +02:00
let toplevel_offset = get_popup_toplevel_coords(surface);
let mut geometry = surface.with_pending_state(|state| state.positioner.get_geometry());
geometry.loc += toplevel_offset;
2022-12-15 13:07:38 +01:00
let offset = check_constrained(geometry, relative);
2022-07-05 18:46:38 +02:00
2022-09-28 12:01:29 +02:00
if offset.x != 0 || offset.y != 0 {
trace!(?surface, "Unconstraining popup.");
2022-12-15 13:07:38 +01:00
if !unconstrain_flip(&surface, relative) {
if !unconstrain_slide(&surface, relative) {
unconstrain_resize(&surface, relative);
2022-07-05 18:46:38 +02:00
}
}
}
}
2022-12-15 13:07:38 +01:00
fn unconstrain_layer_popup(surface: &PopupSurface, output: &Output, layer_surface: &LayerSurface) {
2022-07-07 19:45:55 +02:00
let map = layer_map_for_output(output);
let layer_geo = map.layer_geometry(layer_surface).unwrap();
// the output_rect represented relative to the parents coordinate system
let mut relative = Rectangle::from_loc_and_size((0, 0), output.geometry().size).as_logical();
2022-07-07 19:45:55 +02:00
relative.loc -= layer_geo.loc;
2023-10-12 12:58:30 +02:00
let toplevel_offset = get_popup_toplevel_coords(surface);
let mut geometry = surface.with_pending_state(|state| state.positioner.get_geometry());
geometry.loc += toplevel_offset;
2022-12-15 13:07:38 +01:00
let offset = check_constrained(geometry, relative);
2022-07-07 19:45:55 +02:00
if offset.x != 0 || offset.y != 0 {
trace!(?surface, "Unconstraining popup.");
2022-12-15 13:07:38 +01:00
if !unconstrain_flip(&surface, relative) {
if !unconstrain_slide(&surface, relative) {
unconstrain_resize(&surface, relative);
2022-07-07 19:45:55 +02:00
}
}
}
}
2022-12-15 13:07:38 +01:00
fn unconstrain_flip(popup: &PopupSurface, toplevel_box: Rectangle<i32, Logical>) -> bool {
2023-10-12 12:58:30 +02:00
let toplevel_offset = get_popup_toplevel_coords(popup);
2022-12-15 13:07:38 +01:00
let positioner = popup.with_pending_state(|state| state.positioner.clone());
2023-10-12 12:58:30 +02:00
let mut geometry = positioner.get_geometry();
geometry.loc += toplevel_offset;
let offset = check_constrained(geometry, toplevel_box);
2022-07-05 18:46:38 +02:00
if offset.x == 0 && offset.y == 0 {
return true;
}
let mut positioner = positioner.clone();
let flip_x = offset.x != 0
&& positioner
.constraint_adjustment
.contains(ConstraintAdjustment::FlipX);
let flip_y = offset.y != 0
&& positioner
.constraint_adjustment
.contains(ConstraintAdjustment::FlipY);
if flip_x {
2022-12-15 13:07:38 +01:00
let old_positioner = positioner.clone();
2022-07-05 18:46:38 +02:00
positioner.anchor_edges = invert_anchor_x(positioner.anchor_edges);
positioner.gravity = invert_gravity_x(positioner.gravity);
2023-10-12 12:58:30 +02:00
geometry = positioner.get_geometry();
geometry.loc += toplevel_offset;
let new_offset = check_constrained(geometry, toplevel_box);
2022-12-15 13:07:38 +01:00
if !(new_offset.x.abs() < offset.x.abs()) {
positioner = old_positioner;
}
2022-07-05 18:46:38 +02:00
}
if flip_y {
2022-12-15 13:07:38 +01:00
let old_positioner = positioner.clone();
2022-07-05 18:46:38 +02:00
positioner.anchor_edges = invert_anchor_y(positioner.anchor_edges);
positioner.gravity = invert_gravity_y(positioner.gravity);
2023-10-12 12:58:30 +02:00
geometry = positioner.get_geometry();
geometry.loc += toplevel_offset;
let new_offset = check_constrained(geometry, toplevel_box);
2022-12-15 13:07:38 +01:00
if !(new_offset.y.abs() < offset.y.abs()) {
positioner = old_positioner;
}
2022-07-05 18:46:38 +02:00
}
2023-10-12 12:58:30 +02:00
geometry = positioner.get_geometry();
geometry.loc += toplevel_offset;
let new_offset = check_constrained(geometry, toplevel_box);
2022-12-15 13:07:38 +01:00
if new_offset.x.abs() < offset.x.abs() || new_offset.y.abs() < offset.y.abs() {
2022-07-05 18:46:38 +02:00
popup.with_pending_state(|state| {
state.geometry = positioner.get_geometry();
state.positioner = positioner;
});
}
2022-12-15 13:07:38 +01:00
new_offset.x == 0 && new_offset.y == 0
2022-07-05 18:46:38 +02:00
}
2022-12-15 13:07:38 +01:00
fn unconstrain_slide(popup: &PopupSurface, toplevel_box: Rectangle<i32, Logical>) -> bool {
2023-10-12 12:58:30 +02:00
let toplevel_offset = get_popup_toplevel_coords(popup);
2022-12-15 13:07:38 +01:00
let positioner = popup.with_pending_state(|state| state.positioner.clone());
2023-10-12 12:58:30 +02:00
let mut geometry = positioner.get_geometry();
geometry.loc += toplevel_offset;
let offset = check_constrained(geometry, toplevel_box);
2022-07-05 18:46:38 +02:00
if offset.x == 0 && offset.y == 0 {
return true;
}
let slide_x = offset.x != 0
&& positioner
.constraint_adjustment
.contains(ConstraintAdjustment::SlideX);
let slide_y = offset.y != 0
&& positioner
.constraint_adjustment
.contains(ConstraintAdjustment::SlideY);
let mut geometry = positioner.get_geometry();
if slide_x {
2022-12-15 13:07:38 +01:00
geometry.loc.x += offset.x.abs().min(geometry.size.w) * offset.x.signum();
2022-07-05 18:46:38 +02:00
}
if slide_y {
2022-12-15 13:07:38 +01:00
geometry.loc.y += offset.y.abs().min(geometry.size.h) * offset.y.signum();
2022-07-05 18:46:38 +02:00
}
let toplevel = get_popup_toplevel_coords(popup);
if slide_x && toplevel.x < toplevel_box.loc.x {
geometry.loc.x += toplevel_box.loc.x - toplevel.x;
}
if slide_y && toplevel.y < toplevel_box.loc.y {
geometry.loc.y += toplevel_box.loc.y - toplevel.y;
}
2023-10-12 12:58:30 +02:00
let mut check_geometry = geometry.clone();
check_geometry.loc += toplevel;
let new_offset = check_constrained(check_geometry, toplevel_box);
2022-12-15 13:07:38 +01:00
if new_offset.x.abs() < offset.x.abs() || new_offset.y.abs() < offset.y.abs() {
2022-07-05 18:46:38 +02:00
popup.with_pending_state(|state| {
state.geometry = geometry;
});
}
2022-12-15 13:07:38 +01:00
new_offset.x == 0 && new_offset.y == 0
2022-07-05 18:46:38 +02:00
}
2022-12-15 13:07:38 +01:00
fn unconstrain_resize(popup: &PopupSurface, toplevel_box: Rectangle<i32, Logical>) -> bool {
2023-10-12 12:58:30 +02:00
let toplevel_offset = get_popup_toplevel_coords(popup);
2022-12-15 13:07:38 +01:00
let positioner = popup.with_pending_state(|state| state.positioner.clone());
2023-10-12 12:58:30 +02:00
let mut geometry = positioner.get_geometry();
geometry.loc += toplevel_offset;
let offset = check_constrained(geometry, toplevel_box);
2022-07-05 18:46:38 +02:00
if offset.x == 0 && offset.y == 0 {
return true;
}
let resize_x = offset.x != 0
&& positioner
.constraint_adjustment
.contains(ConstraintAdjustment::ResizeX);
let resize_y = offset.y != 0
&& positioner
.constraint_adjustment
.contains(ConstraintAdjustment::ResizeY);
let mut geometry = positioner.get_geometry();
if resize_x {
geometry.size.w -= offset.x;
}
if resize_y {
geometry.size.h -= offset.y;
}
2023-10-12 12:58:30 +02:00
let mut check_geometry = geometry.clone();
check_geometry.loc += toplevel_offset;
2022-12-15 13:07:38 +01:00
let offset = check_constrained(geometry, toplevel_box);
2022-07-05 18:46:38 +02:00
if offset.x == 0 && offset.y == 0 {
// no longer constrained
popup.with_pending_state(|state| {
state.geometry = geometry;
});
true
} else {
false
}
}
fn check_constrained(
geometry: Rectangle<i32, Logical>,
toplevel_box: Rectangle<i32, Logical>,
) -> Point<i32, Logical> {
let mut offset = (0, 0).into();
if toplevel_box.contains_rect(geometry) {
2022-07-05 18:46:38 +02:00
return offset;
}
if geometry.loc.x < toplevel_box.loc.x {
offset.x = toplevel_box.loc.x - geometry.loc.x;
} else if geometry.loc.x + geometry.size.w > toplevel_box.loc.x + toplevel_box.size.w {
offset.x = toplevel_box.loc.x + toplevel_box.size.w - (geometry.loc.x + geometry.size.w);
2022-07-05 18:46:38 +02:00
}
if geometry.loc.y < toplevel_box.loc.y {
offset.y = toplevel_box.loc.y - geometry.loc.y;
} else if geometry.loc.y + geometry.size.h > toplevel_box.loc.y + toplevel_box.size.h {
offset.y = toplevel_box.loc.y + toplevel_box.size.h - (geometry.loc.y + geometry.size.h);
2022-07-05 18:46:38 +02:00
}
offset
}
fn get_anchor_point(positioner: &PositionerState) -> Point<i32, Logical> {
let rect = positioner.anchor_rect;
match positioner.anchor_edges {
Anchor::Top => (rect.loc.x + (rect.size.w / 2), rect.loc.y),
Anchor::Bottom => (rect.loc.x + (rect.size.w / 2), rect.loc.y + rect.size.h),
Anchor::Left => (rect.loc.x, rect.loc.y + (rect.size.h / 2)),
Anchor::Right => (rect.loc.x + rect.size.w, rect.loc.y + (rect.size.h / 2)),
Anchor::TopLeft => (rect.loc.x, rect.loc.y),
Anchor::TopRight => (rect.loc.x + rect.size.w, rect.loc.y),
Anchor::BottomLeft => (rect.loc.x, rect.loc.y + rect.size.h),
Anchor::BottomRight => (rect.loc.x + rect.size.w, rect.loc.y + rect.size.h),
Anchor::None | _ => (
rect.loc.x + (rect.size.w / 2),
rect.loc.y + (rect.size.h / 2),
),
}
.into()
}
2022-10-27 16:11:54 +02:00
pub fn get_popup_toplevel(popup: &PopupSurface) -> Option<WlSurface> {
2022-07-05 18:46:38 +02:00
let mut parent = popup.get_parent_surface()?;
while get_role(&parent) == Some(XDG_POPUP_ROLE) {
parent = with_states(&parent, |states| {
states
.data_map
.get::<Mutex<XdgPopupSurfaceRoleAttributes>>()
.unwrap()
.lock()
.unwrap()
.parent
.as_ref()
.cloned()
.unwrap()
});
}
Some(parent)
}
fn get_popup_toplevel_coords(popup: &PopupSurface) -> Point<i32, Logical> {
let mut parent = match popup.get_parent_surface() {
Some(parent) => parent,
None => return (0, 0).into(),
};
let mut offset = (0, 0).into();
while get_role(&parent) == Some(XDG_POPUP_ROLE) {
offset += with_states(&parent, |states| {
states
.data_map
.get::<Mutex<XdgPopupSurfaceRoleAttributes>>()
.unwrap()
.lock()
.unwrap()
.current
.geometry
.loc
});
parent = with_states(&parent, |states| {
states
.data_map
.get::<Mutex<XdgPopupSurfaceRoleAttributes>>()
.unwrap()
.lock()
.unwrap()
.parent
.as_ref()
.cloned()
.unwrap()
});
}
offset += with_states(&parent, |states| {
states
.cached_state
.current::<SurfaceCachedState>()
.geometry
.map(|x| x.loc)
.unwrap_or_else(|| (0, 0).into())
});
offset
}
fn invert_anchor_x(anchor: Anchor) -> Anchor {
match anchor {
Anchor::Left => Anchor::Right,
Anchor::Right => Anchor::Left,
Anchor::TopLeft => Anchor::TopRight,
Anchor::TopRight => Anchor::TopLeft,
Anchor::BottomLeft => Anchor::BottomRight,
Anchor::BottomRight => Anchor::BottomLeft,
x => x,
}
}
fn invert_anchor_y(anchor: Anchor) -> Anchor {
match anchor {
Anchor::Top => Anchor::Bottom,
Anchor::Bottom => Anchor::Top,
Anchor::TopLeft => Anchor::BottomLeft,
Anchor::TopRight => Anchor::BottomRight,
Anchor::BottomLeft => Anchor::TopLeft,
Anchor::BottomRight => Anchor::TopRight,
x => x,
}
}
fn invert_gravity_x(gravity: Gravity) -> Gravity {
match gravity {
Gravity::Left => Gravity::Right,
Gravity::Right => Gravity::Left,
Gravity::TopLeft => Gravity::TopRight,
Gravity::TopRight => Gravity::TopLeft,
Gravity::BottomLeft => Gravity::BottomRight,
Gravity::BottomRight => Gravity::BottomLeft,
x => x,
}
}
fn invert_gravity_y(gravity: Gravity) -> Gravity {
match gravity {
Gravity::Top => Gravity::Bottom,
Gravity::Bottom => Gravity::Top,
Gravity::TopLeft => Gravity::BottomLeft,
Gravity::TopRight => Gravity::BottomRight,
Gravity::BottomLeft => Gravity::TopLeft,
Gravity::BottomRight => Gravity::TopRight,
x => x,
}
}