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,
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, 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) {
138 self.xdg_popup().reposition(position, token);
139 }
140}
141
142impl PopupData {
143 pub fn popup(&self) -> Option<Popup> {
147 let inner = self.inner.upgrade()?;
148 Some(Popup { inner })
149 }
150}
151
152impl Drop for PopupInner {
153 fn drop(&mut self) {
154 self.xdg_popup.destroy();
155 }
156}
157
158#[derive(Debug, Clone)]
159#[non_exhaustive]
160pub struct PopupConfigure {
161 pub position: (i32, i32),
163 pub width: i32,
164 pub height: i32,
165 pub serial: u32,
166 pub kind: ConfigureKind,
167}
168
169#[derive(Debug, Clone)]
170#[non_exhaustive]
171pub enum ConfigureKind {
172 Initial,
174 Reactive,
176 Reposition { token: u32 },
178}
179
180impl PopupConfigure {
181 const STATE_NEW: u32 = 0;
182 const STATE_CONFIGURED: u32 = 1;
183 const STATE_REPOSITION_ACK: u32 = 2;
184}
185
186pub trait PopupHandler: Sized {
187 fn configure(
189 &mut self,
190 conn: &Connection,
191 qh: &QueueHandle<Self>,
192 popup: &Popup,
193 config: PopupConfigure,
194 );
195
196 fn done(&mut self, conn: &Connection, qh: &QueueHandle<Self>, popup: &Popup);
198}
199
200impl<D> Dispatch2<xdg_surface::XdgSurface, D> for PopupData
201where
202 D: PopupHandler,
203{
204 fn event(
205 &self,
206 data: &mut D,
207 xdg_surface: &xdg_surface::XdgSurface,
208 event: xdg_surface::Event,
209 conn: &Connection,
210 qh: &QueueHandle<D>,
211 ) {
212 let popup = match self.popup() {
213 Some(popup) => popup,
214 None => return,
215 };
216 let inner = &popup.inner;
217 match event {
218 xdg_surface::Event::Configure { serial } => {
219 xdg_surface.ack_configure(serial);
220 let x = inner.pending_position.0.load(Relaxed);
221 let y = inner.pending_position.1.load(Relaxed);
222 let width = inner.pending_dimensions.0.load(Relaxed);
223 let height = inner.pending_dimensions.1.load(Relaxed);
224 let kind =
225 match inner.configure_state.swap(PopupConfigure::STATE_CONFIGURED, Relaxed) {
226 PopupConfigure::STATE_NEW => ConfigureKind::Initial,
227 PopupConfigure::STATE_CONFIGURED => ConfigureKind::Reactive,
228 PopupConfigure::STATE_REPOSITION_ACK => {
229 ConfigureKind::Reposition { token: inner.pending_token.load(Relaxed) }
230 }
231 _ => unreachable!(),
232 };
233
234 let config = PopupConfigure { position: (x, y), width, height, serial, kind };
235
236 data.configure(conn, qh, &popup, config);
237 }
238 _ => unreachable!(),
239 }
240 }
241}
242
243impl<D> Dispatch2<xdg_popup::XdgPopup, D> for PopupData
244where
245 D: PopupHandler,
246{
247 fn event(
248 &self,
249 data: &mut D,
250 _: &xdg_popup::XdgPopup,
251 event: xdg_popup::Event,
252 conn: &Connection,
253 qh: &QueueHandle<D>,
254 ) {
255 let popup = match self.popup() {
256 Some(popup) => popup,
257 None => return,
258 };
259 let inner = &popup.inner;
260 match event {
261 xdg_popup::Event::Configure { x, y, width, height } => {
262 inner.pending_position.0.store(x, Relaxed);
263 inner.pending_position.1.store(y, Relaxed);
264 inner.pending_dimensions.0.store(width, Relaxed);
265 inner.pending_dimensions.1.store(height, Relaxed);
266 }
267 xdg_popup::Event::PopupDone => {
268 data.done(conn, qh, &popup);
269 }
270 xdg_popup::Event::Repositioned { token } => {
271 inner.pending_token.store(token, Relaxed);
272 inner.configure_state.store(PopupConfigure::STATE_REPOSITION_ACK, Relaxed);
273 }
274 _ => unreachable!(),
275 }
276 }
277}