smithay_client_toolkit/shell/xdg/
popup.rs1use crate::{
2 compositor::{Surface, SurfaceData},
3 dispatch2::Dispatch2,
4 error::GlobalError,
5 globals::ProvidesBoundGlobal,
6 shell::{xdg::XdgShellSurface, WaylandSurface},
7};
8use std::sync::{
9 atomic::{AtomicI32, AtomicU32, Ordering::Relaxed},
10 Arc, Weak,
11};
12use wayland_client::{
13 protocol::{wl_compositor::WlCompositor, wl_surface},
14 Connection, Dispatch, Proxy, QueueHandle,
15};
16use wayland_protocols::xdg::shell::client::{xdg_popup, xdg_positioner, xdg_surface, xdg_wm_base};
17
18#[derive(Debug, Clone)]
19pub struct Popup {
20 inner: Arc<PopupInner>,
21}
22
23impl Eq for Popup {}
24impl PartialEq for Popup {
25 fn eq(&self, other: &Popup) -> bool {
26 Arc::ptr_eq(&self.inner, &other.inner)
27 }
28}
29
30#[derive(Debug)]
31pub struct PopupData {
32 inner: Weak<PopupInner>,
33}
34
35#[derive(Debug)]
36struct PopupInner {
37 surface: XdgShellSurface,
38 xdg_popup: xdg_popup::XdgPopup,
39 pending_position: (AtomicI32, AtomicI32),
40 pending_dimensions: (AtomicI32, AtomicI32),
41 pending_token: AtomicU32,
42 configure_state: AtomicU32,
43}
44
45impl Popup {
46 pub fn new<D>(
51 parent: &xdg_surface::XdgSurface,
52 position: &xdg_positioner::XdgPositioner,
53 qh: &QueueHandle<D>,
54 compositor: &impl ProvidesBoundGlobal<WlCompositor, 6>,
55 wm_base: &impl ProvidesBoundGlobal<xdg_wm_base::XdgWmBase, 5>,
56 ) -> Result<Popup, GlobalError>
57 where
58 D: Dispatch<wl_surface::WlSurface, SurfaceData<()>>
59 + Dispatch<xdg_surface::XdgSurface, PopupData>
60 + Dispatch<xdg_popup::XdgPopup, PopupData>
61 + PopupHandler
62 + 'static,
63 {
64 let surface = Surface::new(compositor, qh)?;
65 let popup = Self::from_surface(Some(parent), position, qh, surface, wm_base)?;
66 popup.wl_surface().commit();
67 Ok(popup)
68 }
69
70 pub fn from_surface<D>(
78 parent: Option<&xdg_surface::XdgSurface>,
79 position: &xdg_positioner::XdgPositioner,
80 qh: &QueueHandle<D>,
81 surface: impl Into<Surface>,
82 wm_base: &impl ProvidesBoundGlobal<xdg_wm_base::XdgWmBase, 5>,
83 ) -> Result<Popup, GlobalError>
84 where
85 D: Dispatch<xdg_surface::XdgSurface, PopupData>
86 + Dispatch<xdg_popup::XdgPopup, PopupData>
87 + 'static,
88 {
89 let surface = surface.into();
90 let wm_base = wm_base.bound_global()?;
91 let freeze = qh.freeze();
94 let inner = Arc::new_cyclic(|weak| {
95 let xdg_surface = wm_base.get_xdg_surface(
96 surface.wl_surface(),
97 qh,
98 PopupData { inner: weak.clone() },
99 );
100 let surface = XdgShellSurface { surface, xdg_surface };
101 let xdg_popup = surface.xdg_surface().get_popup(
102 parent,
103 position,
104 qh,
105 PopupData { inner: weak.clone() },
106 );
107
108 PopupInner {
109 surface,
110 xdg_popup,
111 pending_position: (AtomicI32::new(0), AtomicI32::new(0)),
112 pending_dimensions: (AtomicI32::new(-1), AtomicI32::new(-1)),
113 pending_token: AtomicU32::new(0),
114 configure_state: AtomicU32::new(PopupConfigure::STATE_NEW),
115 }
116 });
117 drop(freeze);
118 Ok(Popup { inner })
119 }
120
121 pub fn xdg_popup(&self) -> &xdg_popup::XdgPopup {
122 &self.inner.xdg_popup
123 }
124
125 pub fn xdg_shell_surface(&self) -> &XdgShellSurface {
126 &self.inner.surface
127 }
128
129 pub fn xdg_surface(&self) -> &xdg_surface::XdgSurface {
130 self.inner.surface.xdg_surface()
131 }
132
133 pub fn wl_surface(&self) -> &wl_surface::WlSurface {
134 self.inner.surface.wl_surface()
135 }
136
137 pub fn reposition(&self, position: &xdg_positioner::XdgPositioner, token: u32) {
140 if self.xdg_popup().version() >= 3 {
141 self.xdg_popup().reposition(position, token);
142 }
143 }
144}
145
146impl WaylandSurface for Popup {
147 fn wl_surface(&self) -> &wl_surface::WlSurface {
148 self.wl_surface()
149 }
150}
151
152impl PopupData {
153 pub fn popup(&self) -> Option<Popup> {
157 let inner = self.inner.upgrade()?;
158 Some(Popup { inner })
159 }
160}
161
162impl Drop for PopupInner {
163 fn drop(&mut self) {
164 self.xdg_popup.destroy();
165 }
166}
167
168#[derive(Debug, Clone)]
169#[non_exhaustive]
170pub struct PopupConfigure {
171 pub position: (i32, i32),
173 pub width: i32,
174 pub height: i32,
175 pub serial: u32,
176 pub kind: ConfigureKind,
177}
178
179#[derive(Debug, Clone)]
180#[non_exhaustive]
181pub enum ConfigureKind {
182 Initial,
184 Reactive,
186 Reposition { token: u32 },
188}
189
190impl PopupConfigure {
191 const STATE_NEW: u32 = 0;
192 const STATE_CONFIGURED: u32 = 1;
193 const STATE_REPOSITION_ACK: u32 = 2;
194}
195
196pub trait PopupHandler: Sized {
197 fn configure(
199 &mut self,
200 conn: &Connection,
201 qh: &QueueHandle<Self>,
202 popup: &Popup,
203 config: PopupConfigure,
204 );
205
206 fn done(&mut self, conn: &Connection, qh: &QueueHandle<Self>, popup: &Popup);
208}
209
210impl<D> Dispatch2<xdg_surface::XdgSurface, D> for PopupData
211where
212 D: PopupHandler,
213{
214 fn event(
215 &self,
216 data: &mut D,
217 xdg_surface: &xdg_surface::XdgSurface,
218 event: xdg_surface::Event,
219 conn: &Connection,
220 qh: &QueueHandle<D>,
221 ) {
222 let popup = match self.popup() {
223 Some(popup) => popup,
224 None => return,
225 };
226 let inner = &popup.inner;
227 match event {
228 xdg_surface::Event::Configure { serial } => {
229 xdg_surface.ack_configure(serial);
230 let x = inner.pending_position.0.load(Relaxed);
231 let y = inner.pending_position.1.load(Relaxed);
232 let width = inner.pending_dimensions.0.load(Relaxed);
233 let height = inner.pending_dimensions.1.load(Relaxed);
234 let kind =
235 match inner.configure_state.swap(PopupConfigure::STATE_CONFIGURED, Relaxed) {
236 PopupConfigure::STATE_NEW => ConfigureKind::Initial,
237 PopupConfigure::STATE_CONFIGURED => ConfigureKind::Reactive,
238 PopupConfigure::STATE_REPOSITION_ACK => {
239 ConfigureKind::Reposition { token: inner.pending_token.load(Relaxed) }
240 }
241 _ => unreachable!(),
242 };
243
244 let config = PopupConfigure { position: (x, y), width, height, serial, kind };
245
246 data.configure(conn, qh, &popup, config);
247 }
248 _ => unreachable!(),
249 }
250 }
251}
252
253impl<D> Dispatch2<xdg_popup::XdgPopup, D> for PopupData
254where
255 D: PopupHandler,
256{
257 fn event(
258 &self,
259 data: &mut D,
260 _: &xdg_popup::XdgPopup,
261 event: xdg_popup::Event,
262 conn: &Connection,
263 qh: &QueueHandle<D>,
264 ) {
265 let popup = match self.popup() {
266 Some(popup) => popup,
267 None => return,
268 };
269 let inner = &popup.inner;
270 match event {
271 xdg_popup::Event::Configure { x, y, width, height } => {
272 inner.pending_position.0.store(x, Relaxed);
273 inner.pending_position.1.store(y, Relaxed);
274 inner.pending_dimensions.0.store(width, Relaxed);
275 inner.pending_dimensions.1.store(height, Relaxed);
276 }
277 xdg_popup::Event::PopupDone => {
278 data.done(conn, qh, &popup);
279 }
280 xdg_popup::Event::Repositioned { token } => {
281 inner.pending_token.store(token, Relaxed);
282 inner.configure_state.store(PopupConfigure::STATE_REPOSITION_ACK, Relaxed);
283 }
284 _ => unreachable!(),
285 }
286 }
287}