| 1 | use crate::{ |
| 2 | compositor::{Surface, SurfaceData}, |
| 3 | error::GlobalError, |
| 4 | globals::ProvidesBoundGlobal, |
| 5 | shell::xdg::XdgShellSurface, |
| 6 | }; |
| 7 | use std::sync::{ |
| 8 | atomic::{AtomicI32, AtomicU32, Ordering::Relaxed}, |
| 9 | Arc, Weak, |
| 10 | }; |
| 11 | use wayland_client::{ |
| 12 | protocol::{wl_compositor::WlCompositor, wl_surface}, |
| 13 | Connection, Dispatch, QueueHandle, |
| 14 | }; |
| 15 | use wayland_protocols::xdg::shell::client::{xdg_popup, xdg_positioner, xdg_surface, xdg_wm_base}; |
| 16 | |
| 17 | #[derive (Debug, Clone)] |
| 18 | pub struct Popup { |
| 19 | inner: Arc<PopupInner>, |
| 20 | } |
| 21 | |
| 22 | impl Eq for Popup {} |
| 23 | impl PartialEq for Popup { |
| 24 | fn eq(&self, other: &Popup) -> bool { |
| 25 | Arc::ptr_eq(&self.inner, &other.inner) |
| 26 | } |
| 27 | } |
| 28 | |
| 29 | #[derive (Debug)] |
| 30 | pub struct PopupData { |
| 31 | inner: Weak<PopupInner>, |
| 32 | } |
| 33 | |
| 34 | #[derive (Debug)] |
| 35 | struct PopupInner { |
| 36 | surface: XdgShellSurface, |
| 37 | xdg_popup: xdg_popup::XdgPopup, |
| 38 | pending_position: (AtomicI32, AtomicI32), |
| 39 | pending_dimensions: (AtomicI32, AtomicI32), |
| 40 | pending_token: AtomicU32, |
| 41 | configure_state: AtomicU32, |
| 42 | } |
| 43 | |
| 44 | impl Popup { |
| 45 | /// Create a new popup. |
| 46 | /// |
| 47 | /// This creates the popup and sends the initial commit. You must wait for |
| 48 | /// [`PopupHandler::configure`] to commit contents to the surface. |
| 49 | pub fn new<D>( |
| 50 | parent: &xdg_surface::XdgSurface, |
| 51 | position: &xdg_positioner::XdgPositioner, |
| 52 | qh: &QueueHandle<D>, |
| 53 | compositor: &impl ProvidesBoundGlobal<WlCompositor, 6>, |
| 54 | wm_base: &impl ProvidesBoundGlobal<xdg_wm_base::XdgWmBase, 5>, |
| 55 | ) -> Result<Popup, GlobalError> |
| 56 | where |
| 57 | D: Dispatch<wl_surface::WlSurface, SurfaceData> |
| 58 | + Dispatch<xdg_surface::XdgSurface, PopupData> |
| 59 | + Dispatch<xdg_popup::XdgPopup, PopupData> |
| 60 | + PopupHandler |
| 61 | + 'static, |
| 62 | { |
| 63 | let surface = Surface::new(compositor, qh)?; |
| 64 | let popup = Self::from_surface(Some(parent), position, qh, surface, wm_base)?; |
| 65 | popup.wl_surface().commit(); |
| 66 | Ok(popup) |
| 67 | } |
| 68 | |
| 69 | /// Create a new popup from an existing surface. |
| 70 | /// |
| 71 | /// If you do not specify a parent surface, you must configure the parent using an alternate |
| 72 | /// function such as [`LayerSurface::get_popup`] prior to committing the surface, or you will |
| 73 | /// get an `invalid_popup_parent` protocol error. |
| 74 | /// |
| 75 | /// [`LayerSurface::get_popup`]: wayland_protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1::get_popup |
| 76 | pub fn from_surface<D>( |
| 77 | parent: Option<&xdg_surface::XdgSurface>, |
| 78 | position: &xdg_positioner::XdgPositioner, |
| 79 | qh: &QueueHandle<D>, |
| 80 | surface: impl Into<Surface>, |
| 81 | wm_base: &impl ProvidesBoundGlobal<xdg_wm_base::XdgWmBase, 5>, |
| 82 | ) -> Result<Popup, GlobalError> |
| 83 | where |
| 84 | D: Dispatch<xdg_surface::XdgSurface, PopupData> |
| 85 | + Dispatch<xdg_popup::XdgPopup, PopupData> |
| 86 | + 'static, |
| 87 | { |
| 88 | let surface = surface.into(); |
| 89 | let wm_base = wm_base.bound_global()?; |
| 90 | // Freeze the queue during the creation of the Arc to avoid a race between events on the |
| 91 | // new objects being processed and the Weak in the PopupData becoming usable. |
| 92 | let freeze = qh.freeze(); |
| 93 | let inner = Arc::new_cyclic(|weak| { |
| 94 | let xdg_surface = wm_base.get_xdg_surface( |
| 95 | surface.wl_surface(), |
| 96 | qh, |
| 97 | PopupData { inner: weak.clone() }, |
| 98 | ); |
| 99 | let surface = XdgShellSurface { surface, xdg_surface }; |
| 100 | let xdg_popup = surface.xdg_surface().get_popup( |
| 101 | parent, |
| 102 | position, |
| 103 | qh, |
| 104 | PopupData { inner: weak.clone() }, |
| 105 | ); |
| 106 | |
| 107 | PopupInner { |
| 108 | surface, |
| 109 | xdg_popup, |
| 110 | pending_position: (AtomicI32::new(0), AtomicI32::new(0)), |
| 111 | pending_dimensions: (AtomicI32::new(-1), AtomicI32::new(-1)), |
| 112 | pending_token: AtomicU32::new(0), |
| 113 | configure_state: AtomicU32::new(PopupConfigure::STATE_NEW), |
| 114 | } |
| 115 | }); |
| 116 | drop(freeze); |
| 117 | Ok(Popup { inner }) |
| 118 | } |
| 119 | |
| 120 | pub fn xdg_popup(&self) -> &xdg_popup::XdgPopup { |
| 121 | &self.inner.xdg_popup |
| 122 | } |
| 123 | |
| 124 | pub fn xdg_shell_surface(&self) -> &XdgShellSurface { |
| 125 | &self.inner.surface |
| 126 | } |
| 127 | |
| 128 | pub fn xdg_surface(&self) -> &xdg_surface::XdgSurface { |
| 129 | self.inner.surface.xdg_surface() |
| 130 | } |
| 131 | |
| 132 | pub fn wl_surface(&self) -> &wl_surface::WlSurface { |
| 133 | self.inner.surface.wl_surface() |
| 134 | } |
| 135 | |
| 136 | pub fn reposition(&self, position: &xdg_positioner::XdgPositioner, token: u32) { |
| 137 | self.xdg_popup().reposition(position, token); |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | impl PopupData { |
| 142 | /// Get a new handle to the Popup |
| 143 | /// |
| 144 | /// This returns `None` if the popup has been destroyed. |
| 145 | pub fn popup(&self) -> Option<Popup> { |
| 146 | let inner: Arc = self.inner.upgrade()?; |
| 147 | Some(Popup { inner }) |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | impl Drop for PopupInner { |
| 152 | fn drop(&mut self) { |
| 153 | self.xdg_popup.destroy(); |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | #[derive (Debug, Clone)] |
| 158 | #[non_exhaustive ] |
| 159 | pub struct PopupConfigure { |
| 160 | /// (x,y) relative to parent surface window geometry |
| 161 | pub position: (i32, i32), |
| 162 | pub width: i32, |
| 163 | pub height: i32, |
| 164 | pub serial: u32, |
| 165 | pub kind: ConfigureKind, |
| 166 | } |
| 167 | |
| 168 | #[derive (Debug, Clone)] |
| 169 | #[non_exhaustive ] |
| 170 | pub enum ConfigureKind { |
| 171 | /// Initial configure for this popup |
| 172 | Initial, |
| 173 | /// The configure is due to an xdg_positioner with set_reactive requested |
| 174 | Reactive, |
| 175 | /// The configure is due to a reposition request with this token |
| 176 | Reposition { token: u32 }, |
| 177 | } |
| 178 | |
| 179 | impl PopupConfigure { |
| 180 | const STATE_NEW: u32 = 0; |
| 181 | const STATE_CONFIGURED: u32 = 1; |
| 182 | const STATE_REPOSITION_ACK: u32 = 2; |
| 183 | } |
| 184 | |
| 185 | pub trait PopupHandler: Sized { |
| 186 | /// The popup has been configured. |
| 187 | fn configure( |
| 188 | &mut self, |
| 189 | conn: &Connection, |
| 190 | qh: &QueueHandle<Self>, |
| 191 | popup: &Popup, |
| 192 | config: PopupConfigure, |
| 193 | ); |
| 194 | |
| 195 | /// The popup was dismissed by the compositor and should be destroyed. |
| 196 | fn done(&mut self, conn: &Connection, qh: &QueueHandle<Self>, popup: &Popup); |
| 197 | } |
| 198 | |
| 199 | impl<D> Dispatch<xdg_surface::XdgSurface, PopupData, D> for PopupData |
| 200 | where |
| 201 | D: Dispatch<xdg_surface::XdgSurface, PopupData> + PopupHandler, |
| 202 | { |
| 203 | fn event( |
| 204 | data: &mut D, |
| 205 | xdg_surface: &xdg_surface::XdgSurface, |
| 206 | event: xdg_surface::Event, |
| 207 | pdata: &PopupData, |
| 208 | conn: &Connection, |
| 209 | qh: &QueueHandle<D>, |
| 210 | ) { |
| 211 | let popup = match pdata.popup() { |
| 212 | Some(popup) => popup, |
| 213 | None => return, |
| 214 | }; |
| 215 | let inner = &popup.inner; |
| 216 | match event { |
| 217 | xdg_surface::Event::Configure { serial } => { |
| 218 | xdg_surface.ack_configure(serial); |
| 219 | let x = inner.pending_position.0.load(Relaxed); |
| 220 | let y = inner.pending_position.1.load(Relaxed); |
| 221 | let width = inner.pending_dimensions.0.load(Relaxed); |
| 222 | let height = inner.pending_dimensions.1.load(Relaxed); |
| 223 | let kind = |
| 224 | match inner.configure_state.swap(PopupConfigure::STATE_CONFIGURED, Relaxed) { |
| 225 | PopupConfigure::STATE_NEW => ConfigureKind::Initial, |
| 226 | PopupConfigure::STATE_CONFIGURED => ConfigureKind::Reactive, |
| 227 | PopupConfigure::STATE_REPOSITION_ACK => { |
| 228 | ConfigureKind::Reposition { token: inner.pending_token.load(Relaxed) } |
| 229 | } |
| 230 | _ => unreachable!(), |
| 231 | }; |
| 232 | |
| 233 | let config = PopupConfigure { position: (x, y), width, height, serial, kind }; |
| 234 | |
| 235 | data.configure(conn, qh, &popup, config); |
| 236 | } |
| 237 | _ => unreachable!(), |
| 238 | } |
| 239 | } |
| 240 | } |
| 241 | |
| 242 | impl<D> Dispatch<xdg_popup::XdgPopup, PopupData, D> for PopupData |
| 243 | where |
| 244 | D: Dispatch<xdg_popup::XdgPopup, PopupData> + PopupHandler, |
| 245 | { |
| 246 | fn event( |
| 247 | data: &mut D, |
| 248 | _: &xdg_popup::XdgPopup, |
| 249 | event: xdg_popup::Event, |
| 250 | pdata: &PopupData, |
| 251 | conn: &Connection, |
| 252 | qh: &QueueHandle<D>, |
| 253 | ) { |
| 254 | let popup = match pdata.popup() { |
| 255 | Some(popup) => popup, |
| 256 | None => return, |
| 257 | }; |
| 258 | let inner = &popup.inner; |
| 259 | match event { |
| 260 | xdg_popup::Event::Configure { x, y, width, height } => { |
| 261 | inner.pending_position.0.store(x, Relaxed); |
| 262 | inner.pending_position.1.store(y, Relaxed); |
| 263 | inner.pending_dimensions.0.store(width, Relaxed); |
| 264 | inner.pending_dimensions.1.store(height, Relaxed); |
| 265 | } |
| 266 | xdg_popup::Event::PopupDone => { |
| 267 | data.done(conn, qh, &popup); |
| 268 | } |
| 269 | xdg_popup::Event::Repositioned { token } => { |
| 270 | inner.pending_token.store(token, Relaxed); |
| 271 | inner.configure_state.store(PopupConfigure::STATE_REPOSITION_ACK, Relaxed); |
| 272 | } |
| 273 | _ => unreachable!(), |
| 274 | } |
| 275 | } |
| 276 | } |
| 277 | |
| 278 | #[macro_export ] |
| 279 | macro_rules! delegate_xdg_popup { |
| 280 | ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { |
| 281 | $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ |
| 282 | $crate::reexports::protocols::xdg::shell::client::xdg_popup::XdgPopup: $crate::shell::xdg::popup::PopupData |
| 283 | ] => $crate::shell::xdg::popup::PopupData); |
| 284 | $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ |
| 285 | $crate::reexports::protocols::xdg::shell::client::xdg_surface::XdgSurface: $crate::shell::xdg::popup::PopupData |
| 286 | ] => $crate::shell::xdg::popup::PopupData); |
| 287 | }; |
| 288 | } |
| 289 | |