use crate::{
LogicalRect, LogicalSize, PointerMode, PopupDesc, PopupInput, PopupPlacement, ShellBackend,
ShellError, WindowConfiguration, WindowDesc, WindowId,
};
use core::ptr;
use super::ffi::WlProxy;
use super::protocol::layer_shell::zwlr_layer_surface_v1;
use super::protocol::wayland::{wl_compositor, wl_region, wl_surface};
use super::protocol::xdg_shell::{xdg_popup, xdg_positioner, xdg_surface, xdg_wm_base};
use super::{ObjectKind, PendingConfigure, WaylandShell, WlWindow, optional, round_logical};
const fn positioner_of(placement: PopupPlacement) -> (u32, u32, u32) {
use xdg_positioner::{anchor, constraint_adjustment as adjust, gravity};
match placement {
PopupPlacement::Below => (
anchor::BOTTOM_LEFT,
gravity::BOTTOM_RIGHT,
adjust::FLIP_Y | adjust::SLIDE_X,
),
PopupPlacement::Above => (
anchor::TOP_LEFT,
gravity::TOP_RIGHT,
adjust::FLIP_Y | adjust::SLIDE_X,
),
PopupPlacement::Left => (
anchor::TOP_LEFT,
gravity::BOTTOM_LEFT,
adjust::FLIP_X | adjust::SLIDE_Y,
),
PopupPlacement::Right => (
anchor::TOP_RIGHT,
gravity::BOTTOM_RIGHT,
adjust::FLIP_X | adjust::SLIDE_Y,
),
}
}
pub(super) const REPOSITION_SINCE: u32 = 3;
const REPOSITION_TOKEN: u32 = 0;
unsafe fn positioner(
wm_base: *mut WlProxy,
size: LogicalSize,
anchor_rect: LogicalRect,
placement: PopupPlacement,
) -> *mut WlProxy {
let (anchor, gravity, adjustment) = positioner_of(placement);
let LogicalRect {
origin,
size: extent_of,
} = anchor_rect;
unsafe {
let positioner = xdg_wm_base::create_positioner(wm_base);
if positioner.is_null() {
return positioner;
}
xdg_positioner::set_size(positioner, extent(size.width), extent(size.height));
xdg_positioner::set_anchor_rect(
positioner,
round_logical(origin.x),
round_logical(origin.y),
extent(extent_of.width),
extent(extent_of.height),
);
xdg_positioner::set_anchor(positioner, anchor);
xdg_positioner::set_gravity(positioner, gravity);
xdg_positioner::set_constraint_adjustment(positioner, adjustment);
positioner
}
}
fn extent(value: f64) -> i32 {
round_logical(value).max(1)
}
impl WaylandShell {
pub(super) fn create_popup(
&mut self,
desc: &WindowDesc<'_>,
popup: &PopupDesc,
) -> Result<WindowId, ShellError> {
let parent = self.window(popup.parent)?;
let (parent_xdg, parent_layer) = (parent.xdg_surface, parent.layer_surface);
if self.conn.compositor.is_null() || self.conn.wm_base.is_null() {
return Err(ShellError::WindowCreation(
"the compositor withdrew wl_compositor or xdg_wm_base".to_string(),
));
}
let grab = match popup.input {
PopupInput::Grab => Some(self.grab_serial()?),
PopupInput::Passthrough | PopupInput::Pointer => None,
};
let wm_base = self.conn.wm_base;
let compositor = self.conn.compositor;
let (surface, xdg, popup_proxy) = unsafe {
let surface = wl_compositor::create_surface(compositor);
if surface.is_null() {
return Err(ShellError::WindowCreation(
"wl_compositor.create_surface failed".to_string(),
));
}
let positioner = positioner(wm_base, desc.size, popup.anchor, popup.placement);
if positioner.is_null() {
self.conn.release(surface, wl_surface::destroy);
return Err(ShellError::WindowCreation(
"xdg_wm_base.create_positioner failed".to_string(),
));
}
let xdg = xdg_wm_base::get_xdg_surface(wm_base, surface);
if xdg.is_null() {
xdg_positioner::destroy(positioner);
self.conn.release(surface, wl_surface::destroy);
return Err(ShellError::WindowCreation(
"xdg_wm_base.get_xdg_surface failed".to_string(),
));
}
let xdg_parent = if parent_layer.is_null() {
parent_xdg
} else {
ptr::null_mut()
};
let popup_proxy = xdg_surface::get_popup(xdg, xdg_parent, positioner);
xdg_positioner::destroy(positioner);
if popup_proxy.is_null() {
self.conn.release(xdg, xdg_surface::destroy);
self.conn.release(surface, wl_surface::destroy);
return Err(ShellError::WindowCreation(
"xdg_surface.get_popup failed".to_string(),
));
}
if !parent_layer.is_null() {
zwlr_layer_surface_v1::get_popup(parent_layer, popup_proxy);
}
(surface, xdg, popup_proxy)
};
self.conn.watch(surface, ObjectKind::Surface);
self.conn.watch(xdg, ObjectKind::XdgSurface);
self.conn.watch(popup_proxy, ObjectKind::XdgPopup);
let (fractional_scale, viewport) = unsafe {
(
optional(self.conn.fractional_scale_manager, |manager| {
super::wp_fractional_scale_manager_v1::get_fractional_scale(manager, surface)
}),
optional(self.conn.viewporter, |manager| {
super::wp_viewporter::get_viewport(manager, surface)
}),
)
};
self.conn
.watch(fractional_scale, ObjectKind::FractionalScale);
self.conn.watch(viewport, ObjectKind::Ignored);
unsafe {
if popup.input == PopupInput::Passthrough {
let region = wl_compositor::create_region(compositor);
if !region.is_null() {
wl_surface::set_input_region(surface, region);
wl_region::destroy(region);
}
}
if let Some((seat, serial)) = grab {
xdg_popup::grab(popup_proxy, seat, serial);
}
wl_surface::commit(surface);
}
self.conn.flush();
let handle: WindowId = self
.windows
.insert(WlWindow {
surface,
xdg_surface: xdg,
toplevel: ptr::null_mut(),
layer_surface: ptr::null_mut(),
popup: popup_proxy,
parent: Some(popup.parent),
placement: Some(popup.placement),
popup_input: Some(popup.input),
panel_monitor: None,
decoration: ptr::null_mut(),
fractional_scale,
viewport,
title: desc.title.to_string(),
requested_size: desc.size,
requested_mode: desc.mode,
requested_constraints: desc.constraints,
resizable: desc.resizable,
configuration: None::<WindowConfiguration>,
pending: PendingConfigure::default(),
outputs: Vec::new(),
scale: 1,
preferred_scale: None,
pointer_mode: PointerMode::Free,
constraints: Vec::new(),
cursor: None,
accept_drops: desc.accept_drops,
transparent: desc.transparent,
text_input: super::text_input::Field::default(),
focused: false,
visible: desc.visible,
close_pending: false,
})
.cast();
Ok(handle)
}
pub(super) fn move_popup(
&mut self,
window: WindowId,
anchor: LogicalRect,
size: LogicalSize,
) -> Result<(), ShellError> {
if !self.caps.contains(crate::ShellCaps::POPUP_REPOSITION) {
return Err(ShellError::Unsupported {
backend: ShellBackend::Wayland,
what: "repositioning a popup, which needs xdg_wm_base 3",
});
}
let state = self.window(window)?;
let (proxy, placement) = match state.placement {
Some(placement) if !state.popup.is_null() => (state.popup, placement),
_ => return Err(crate::role::not_a_popup(window)),
};
unsafe {
let positioner = positioner(self.conn.wm_base, size, anchor, placement);
if positioner.is_null() {
return Err(ShellError::Backend(
"xdg_wm_base.create_positioner failed".to_string(),
));
}
xdg_popup::reposition(proxy, positioner, REPOSITION_TOKEN);
xdg_positioner::destroy(positioner);
}
self.conn.flush();
self.window_mut(window)?.requested_size = size;
Ok(())
}
fn grab_serial(&self) -> Result<(*mut WlProxy, u32), ShellError> {
self.seats
.iter()
.filter(|seat| seat.last_serial != 0)
.max_by_key(|seat| seat.last_serial)
.map(|seat| (seat.proxy, seat.last_serial))
.ok_or(ShellError::NeedsUserInteraction {
backend: ShellBackend::Wayland,
what: "a popup that grabs the pointer and the keyboard",
})
}
}
#[cfg(test)]
mod tests {
use super::{extent, positioner_of};
use crate::PopupPlacement;
#[test]
fn each_placement_is_an_anchor_a_gravity_and_room_to_flip() {
assert_eq!(positioner_of(PopupPlacement::Below), (6, 8, 8 | 1));
assert_eq!(positioner_of(PopupPlacement::Above), (5, 7, 8 | 1));
assert_eq!(positioner_of(PopupPlacement::Left), (5, 6, 4 | 2));
assert_eq!(positioner_of(PopupPlacement::Right), (7, 8, 4 | 2));
}
#[test]
fn a_positioner_extent_is_never_zero() {
assert_eq!(extent(0.0), 1);
assert_eq!(extent(199.6), 200);
assert_eq!(extent(-3.0), 1);
}
}