add v3 positioner fields and handle requests

add support for xdg_popup reposition
increment supported wm_base version to 3
This commit is contained in:
Christian Meissl 2021-08-03 15:43:40 +02:00
parent dbd03567ff
commit b36cfbb392
4 changed files with 173 additions and 26 deletions

View File

@ -374,12 +374,31 @@ pub fn init_shell<BackendData: 'static>(display: Rc<RefCell<Display>>, log: ::sl
.borrow_mut() .borrow_mut()
.insert(SurfaceKind::Xdg(surface), (x, y).into()); .insert(SurfaceKind::Xdg(surface), (x, y).into());
} }
XdgRequest::NewPopup { surface } => { XdgRequest::NewPopup { surface, .. } => {
// Do not send a configure here, the initial configure // Do not send a configure here, the initial configure
// of a xdg_surface has to be sent during the commit if // of a xdg_surface has to be sent during the commit if
// the surface is not already configured // the surface is not already configured
xdg_window_map.borrow_mut().insert_popup(PopupKind::Xdg(surface)); xdg_window_map.borrow_mut().insert_popup(PopupKind::Xdg(surface));
} }
XdgRequest::RePosition {
surface,
positioner,
token,
} => {
let result = surface.with_pending_state(|state| {
// NOTE: This is again a simplification, a proper compositor would
// calculate the geometry of the popup here. For simplicity we just
// use the default implementation here that does not take the
// window position and output constraints into account.
let geometry = positioner.get_geometry();
state.geometry = geometry;
state.positioner = positioner;
});
if result.is_ok() {
surface.send_repositioned(token);
}
}
XdgRequest::Move { XdgRequest::Move {
surface, surface,
seat, seat,

View File

@ -315,12 +315,6 @@ xdg_role!(
/// It is a protocol error to call commit on a wl_surface with /// It is a protocol error to call commit on a wl_surface with
/// the xdg_popup role when no parent is set. /// the xdg_popup role when no parent is set.
pub parent: Option<wl_surface::WlSurface>, pub parent: Option<wl_surface::WlSurface>,
/// The positioner state can be used by the compositor
/// to calculate the best placement for the popup.
///
/// For example the compositor should prevent that a popup
/// is placed outside the visible rectangle of a output.
pub positioner: PositionerState,
/// Holds the last server_pending state that has been acknowledged /// Holds the last server_pending state that has been acknowledged
/// by the client. This state should be cloned to the current /// by the client. This state should be cloned to the current
/// during a commit. /// during a commit.
@ -340,6 +334,12 @@ xdg_role!(
/// Represents the state of the popup /// Represents the state of the popup
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub struct PopupState { pub struct PopupState {
/// The positioner state can be used by the compositor
/// to calculate the best placement for the popup.
///
/// For example the compositor should prevent that a popup
/// is placed outside the visible rectangle of a output.
pub positioner: PositionerState,
/// Holds the geometry of the popup as defined by the positioner. /// Holds the geometry of the popup as defined by the positioner.
/// ///
/// `Rectangle::width` and `Rectangle::height` holds the size of the /// `Rectangle::width` and `Rectangle::height` holds the size of the
@ -356,11 +356,12 @@ impl Default for PopupState {
fn default() -> Self { fn default() -> Self {
Self { Self {
geometry: Default::default(), geometry: Default::default(),
positioner: Default::default(),
} }
} }
} }
#[derive(Clone, Debug)] #[derive(Clone, Copy, Debug, PartialEq)]
/// The state of a positioner, as set by the client /// The state of a positioner, as set by the client
pub struct PositionerState { pub struct PositionerState {
/// Size of the rectangle that needs to be positioned /// Size of the rectangle that needs to be positioned
@ -378,6 +379,26 @@ pub struct PositionerState {
pub constraint_adjustment: xdg_positioner::ConstraintAdjustment, pub constraint_adjustment: xdg_positioner::ConstraintAdjustment,
/// Offset placement relative to the anchor point /// Offset placement relative to the anchor point
pub offset: Point<i32, Logical>, pub offset: Point<i32, Logical>,
/// When set reactive, the surface is reconstrained if the conditions
/// used for constraining changed, e.g. the parent window moved.
///
/// If the conditions changed and the popup was reconstrained,
/// an xdg_popup.configure event is sent with updated geometry,
/// followed by an xdg_surface.configure event.
pub reactive: bool,
/// The parent window geometry the compositor should use when
/// positioning the popup. The compositor may use this information
/// to determine the future state the popup should be constrained using.
/// If this doesn't match the dimension of the parent the popup is
/// eventually positioned against, the behavior is undefined.
///
/// The arguments are given in the surface-local coordinate space.
pub parent_size: Option<Size<i32, Logical>>,
/// The serial of an xdg_surface.configure event this positioner will
/// be used in response to. The compositor may use this information
/// together with set_parent_size to determine what future state the
/// popup should be constrained using.
pub parent_configure: Option<Serial>,
} }
impl Default for PositionerState { impl Default for PositionerState {
@ -389,6 +410,9 @@ impl Default for PositionerState {
gravity: xdg_positioner::Gravity::None, gravity: xdg_positioner::Gravity::None,
offset: Default::default(), offset: Default::default(),
rect_size: Default::default(), rect_size: Default::default(),
reactive: false,
parent_size: None,
parent_configure: None,
} }
} }
} }
@ -458,7 +482,7 @@ impl PositionerState {
/// The `constraint_adjustment` will not be considered by this /// The `constraint_adjustment` will not be considered by this
/// implementation and the position and size should be re-calculated /// implementation and the position and size should be re-calculated
/// in the compositor if the compositor implements `constraint_adjustment` /// in the compositor if the compositor implements `constraint_adjustment`
pub(crate) fn get_geometry(&self) -> Rectangle<i32, Logical> { pub fn get_geometry(&self) -> Rectangle<i32, Logical> {
// From the `xdg_shell` prococol specification: // From the `xdg_shell` prococol specification:
// //
// set_offset: // set_offset:
@ -700,7 +724,7 @@ where
let shell_data_z = shell_data.clone(); let shell_data_z = shell_data.clone();
let xdg_shell_global = display.create_global( let xdg_shell_global = display.create_global(
1, 3,
Filter::new(move |(shell, _version), _, dispatch_data| { Filter::new(move |(shell, _version), _, dispatch_data| {
self::xdg_handlers::implement_wm_base(shell, &shell_data, dispatch_data); self::xdg_handlers::implement_wm_base(shell, &shell_data, dispatch_data);
}), }),
@ -1269,13 +1293,9 @@ impl PopupSurface {
Some(ShellClient { kind: shell }) Some(ShellClient { kind: shell })
} }
/// Send a configure event to this popup surface to suggest it a new configuration /// Internal configure function to re-use the configure
/// /// logic for both [`XdgRequest::send_configure`] and [`XdgRequest::send_repositioned`]
/// The serial of this configure will be tracked waiting for the client to ACK it. fn send_configure_internal(&self, reposition_token: Option<u32>) {
///
/// You can manipulate the state that will be sent to the client with the [`with_pending_state`](#method.with_pending_state)
/// method.
pub fn send_configure(&self) {
if let Some(surface) = self.get_surface() { if let Some(surface) = self.get_surface() {
let next_configure = compositor::with_states(surface, |states| { let next_configure = compositor::with_states(surface, |states| {
let mut attributes = states let mut attributes = states
@ -1284,12 +1304,16 @@ impl PopupSurface {
.unwrap() .unwrap()
.lock() .lock()
.unwrap(); .unwrap();
if !attributes.initial_configure_sent || attributes.server_pending.is_some() { if !attributes.initial_configure_sent
|| attributes.server_pending.is_some()
|| reposition_token.is_some()
{
let pending = attributes.server_pending.take().unwrap_or(attributes.current); let pending = attributes.server_pending.take().unwrap_or(attributes.current);
let configure = PopupConfigure { let configure = PopupConfigure {
state: pending, state: pending,
serial: SERIAL_COUNTER.next_serial(), serial: SERIAL_COUNTER.next_serial(),
reposition_token,
}; };
attributes.pending_configures.push(configure); attributes.pending_configures.push(configure);
@ -1314,6 +1338,24 @@ impl PopupSurface {
} }
} }
/// Send a configure event to this popup surface to suggest it a new configuration
///
/// The serial of this configure will be tracked waiting for the client to ACK it.
///
/// You can manipulate the state that will be sent to the client with the [`with_pending_state`](#method.with_pending_state)
/// method.
pub fn send_configure(&self) {
self.send_configure_internal(None)
}
/// Send a configure event, including the `repositioned` event to the client
/// in response to a `reposition` request.
///
/// For further information see [`XdgPopup::send_configure`]
pub fn send_repositioned(&self, token: u32) {
self.send_configure_internal(Some(token))
}
/// Handles the role specific commit logic /// Handles the role specific commit logic
/// ///
/// This should be called when the underlying WlSurface /// This should be called when the underlying WlSurface
@ -1436,6 +1478,10 @@ impl PopupSurface {
/// It means that the use has dismissed the popup surface, or that /// It means that the use has dismissed the popup surface, or that
/// the pointer has left the area of popup grab if there was a grab. /// the pointer has left the area of popup grab if there was a grab.
pub fn send_popup_done(&self) { pub fn send_popup_done(&self) {
if !self.alive() {
return;
}
match self.shell_surface { match self.shell_surface {
PopupKind::Xdg(ref p) => p.popup_done(), PopupKind::Xdg(ref p) => p.popup_done(),
PopupKind::ZxdgV6(ref p) => p.popup_done(), PopupKind::ZxdgV6(ref p) => p.popup_done(),
@ -1511,6 +1557,10 @@ pub struct PopupConfigure {
/// from a client for a serial will validate all pending lower /// from a client for a serial will validate all pending lower
/// serials. /// serials.
pub serial: Serial, pub serial: Serial,
/// The token the client provided in the `xdg_popup::reposition`
/// request. The token itself is opaque, and has no other special meaning.
/// The token is sent in the corresponding `xdg_popup::repositioned` event.
pub reposition_token: Option<u32>,
} }
/// Defines the possible configure variants /// Defines the possible configure variants
@ -1574,6 +1624,15 @@ pub enum XdgRequest {
NewPopup { NewPopup {
/// the surface /// the surface
surface: PopupSurface, surface: PopupSurface,
/// The state of the positioner at the time
/// the popup was requested.
///
/// The positioner state can be used by the compositor
/// to calculate the best placement for the popup.
///
/// For example the compositor should prevent that a popup
/// is placed outside the visible rectangle of a output.
positioner: PositionerState,
}, },
/// The client requested the start of an interactive move for this surface /// The client requested the start of an interactive move for this surface
Move { Move {
@ -1655,4 +1714,24 @@ pub enum XdgRequest {
/// The configure serial. /// The configure serial.
configure: Configure, configure: Configure,
}, },
/// A client requested a reposition, providing a new
/// positioner, of a popup.
RePosition {
/// The popup for which a reposition has been requested
surface: PopupSurface,
/// The state of the positioner at the time
/// the reposition request was made.
///
/// The positioner state can be used by the compositor
/// to calculate the best placement for the popup.
///
/// For example the compositor should prevent that a popup
/// is placed outside the visible rectangle of a output.
positioner: PositionerState,
/// The passed token will be sent in the corresponding xdg_popup.repositioned event.
/// The new popup position will not take effect until the corresponding configure event
/// is acknowledged by the client. See xdg_popup.repositioned for details.
/// The token itself is opaque, and has no other special meaning.
token: u32,
},
} }

View File

@ -158,6 +158,18 @@ fn implement_positioner(positioner: Main<xdg_positioner::XdgPositioner>) -> xdg_
xdg_positioner::Request::SetOffset { x, y } => { xdg_positioner::Request::SetOffset { x, y } => {
state.offset = (x, y).into(); state.offset = (x, y).into();
} }
xdg_positioner::Request::SetReactive => {
state.reactive = true;
}
xdg_positioner::Request::SetParentSize {
parent_width,
parent_height,
} => {
state.parent_size = Some((parent_width, parent_height).into());
}
xdg_positioner::Request::SetParentConfigure { serial } => {
state.parent_configure = Some(Serial::from(serial));
}
_ => unreachable!(), _ => unreachable!(),
} }
}); });
@ -265,13 +277,12 @@ fn xdg_surface_implementation(
parent, parent,
positioner, positioner,
} => { } => {
let positioner_data = positioner let positioner_data = *positioner
.as_ref() .as_ref()
.user_data() .user_data()
.get::<RefCell<PositionerState>>() .get::<RefCell<PositionerState>>()
.unwrap() .unwrap()
.borrow() .borrow();
.clone();
let parent_surface = parent.map(|parent| { let parent_surface = parent.map(|parent| {
let parent_data = parent.as_ref().user_data().get::<XdgSurfaceUserData>().unwrap(); let parent_data = parent.as_ref().user_data().get::<XdgSurfaceUserData>().unwrap();
@ -287,6 +298,7 @@ fn xdg_surface_implementation(
server_pending: Some(PopupState { server_pending: Some(PopupState {
// Set the positioner data as the popup geometry // Set the positioner data as the popup geometry
geometry: positioner_data.get_geometry(), geometry: positioner_data.get_geometry(),
positioner: positioner_data,
}), }),
..Default::default() ..Default::default()
}; };
@ -333,7 +345,13 @@ fn xdg_surface_implementation(
let handle = make_popup_handle(&id); let handle = make_popup_handle(&id);
let mut user_impl = data.shell_data.user_impl.borrow_mut(); let mut user_impl = data.shell_data.user_impl.borrow_mut();
(&mut *user_impl)(XdgRequest::NewPopup { surface: handle }, dispatch_data); (&mut *user_impl)(
XdgRequest::NewPopup {
surface: handle,
positioner: positioner_data,
},
dispatch_data,
);
} }
xdg_surface::Request::SetWindowGeometry { x, y, width, height } => { xdg_surface::Request::SetWindowGeometry { x, y, width, height } => {
// Check the role of the surface, this can be either xdg_toplevel // Check the role of the surface, this can be either xdg_toplevel
@ -693,6 +711,11 @@ pub(crate) fn send_popup_configure(resource: &xdg_popup::XdgPopup, configure: Po
let serial = configure.serial; let serial = configure.serial;
let geometry = configure.state.geometry; let geometry = configure.state.geometry;
// Send repositioned if token is set
if let Some(token) = configure.reposition_token {
resource.repositioned(token);
}
// Send the popup configure // Send the popup configure
resource.configure(geometry.loc.x, geometry.loc.y, geometry.size.w, geometry.size.h); resource.configure(geometry.loc.x, geometry.loc.y, geometry.size.w, geometry.size.h);
@ -736,6 +759,26 @@ fn xdg_popup_implementation(
dispatch_data, dispatch_data,
); );
} }
xdg_popup::Request::Reposition { positioner, token } => {
let handle = make_popup_handle(&popup);
let mut user_impl = data.shell_data.user_impl.borrow_mut();
let positioner_data = *positioner
.as_ref()
.user_data()
.get::<RefCell<PositionerState>>()
.unwrap()
.borrow();
(&mut *user_impl)(
XdgRequest::RePosition {
surface: handle,
positioner: positioner_data,
token,
},
dispatch_data,
);
}
_ => unreachable!(), _ => unreachable!(),
} }
} }

View File

@ -279,13 +279,12 @@ fn xdg_surface_implementation(
parent, parent,
positioner, positioner,
} => { } => {
let positioner_data = positioner let positioner_data = *positioner
.as_ref() .as_ref()
.user_data() .user_data()
.get::<RefCell<PositionerState>>() .get::<RefCell<PositionerState>>()
.unwrap() .unwrap()
.borrow() .borrow();
.clone();
let parent_surface = { let parent_surface = {
let parent_data = parent.as_ref().user_data().get::<XdgSurfaceUserData>().unwrap(); let parent_data = parent.as_ref().user_data().get::<XdgSurfaceUserData>().unwrap();
@ -301,6 +300,7 @@ fn xdg_surface_implementation(
server_pending: Some(PopupState { server_pending: Some(PopupState {
// Set the positioner data as the popup geometry // Set the positioner data as the popup geometry
geometry: positioner_data.get_geometry(), geometry: positioner_data.get_geometry(),
positioner: positioner_data,
}), }),
..Default::default() ..Default::default()
}; };
@ -347,7 +347,13 @@ fn xdg_surface_implementation(
let handle = make_popup_handle(&id); let handle = make_popup_handle(&id);
let mut user_impl = data.shell_data.user_impl.borrow_mut(); let mut user_impl = data.shell_data.user_impl.borrow_mut();
(&mut *user_impl)(XdgRequest::NewPopup { surface: handle }, dispatch_data); (&mut *user_impl)(
XdgRequest::NewPopup {
surface: handle,
positioner: positioner_data,
},
dispatch_data,
);
} }
zxdg_surface_v6::Request::SetWindowGeometry { x, y, width, height } => { zxdg_surface_v6::Request::SetWindowGeometry { x, y, width, height } => {
// Check the role of the surface, this can be either xdg_toplevel // Check the role of the surface, this can be either xdg_toplevel