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 | |