smithay_clipboard/dnd/
state.rs

1use std::collections::HashMap;
2use std::io::{Error, ErrorKind, Read, Write};
3use std::mem;
4use std::os::unix::io::AsRawFd;
5
6use sctk::data_device_manager::data_offer::DragOffer;
7use sctk::data_device_manager::data_source::DragSource;
8use sctk::data_device_manager::WritePipe;
9use sctk::reexports::calloop::PostAction;
10use sctk::reexports::client::protocol::wl_data_device::WlDataDevice;
11use sctk::reexports::client::protocol::wl_data_device_manager::DndAction;
12use sctk::reexports::client::protocol::wl_shm::Format;
13use sctk::reexports::client::protocol::wl_surface::WlSurface;
14use sctk::reexports::client::Proxy;
15use wayland_backend::client::ObjectId;
16
17use crate::mime::{AsMimeTypes, MimeType};
18use crate::state::{set_non_blocking, State};
19
20use super::{DndDestinationRectangle, DndEvent, DndRequest, DndSurface, Icon, OfferEvent};
21
22pub(crate) struct DndState<T> {
23    pub(crate) sender: Option<Box<dyn crate::dnd::Sender<T>>>,
24    destinations: HashMap<ObjectId, (DndSurface<T>, Vec<DndDestinationRectangle>)>,
25    pub(crate) dnd_source: Option<DragSource>,
26    active_surface: Option<(DndSurface<T>, Option<DndDestinationRectangle>)>,
27    source_actions: DndAction,
28    selected_action: DndAction,
29    selected_mime: Option<MimeType>,
30    pub(crate) icon_surface: Option<WlSurface>,
31    pub(crate) source_content: Option<Box<dyn AsMimeTypes>>,
32    accept_ctr: u32,
33}
34
35impl<T> Default for DndState<T> {
36    fn default() -> Self {
37        Self {
38            sender: Default::default(),
39            destinations: Default::default(),
40            dnd_source: Default::default(),
41            active_surface: None,
42            source_actions: DndAction::empty(),
43            selected_action: DndAction::empty(),
44            selected_mime: None,
45            source_content: None,
46            accept_ctr: 1,
47            icon_surface: None,
48        }
49    }
50}
51
52impl<T> DndState<T> {
53    pub(crate) fn selected_action(&mut self, a: DndAction) {
54        self.selected_action = a;
55        if let Some(tx) = self.sender.as_ref() {
56            _ = tx.send(DndEvent::Offer(
57                self.active_surface.as_ref().and_then(|(_, d)| d.as_ref().map(|d| d.id)),
58                OfferEvent::SelectedAction(a),
59            ));
60        }
61    }
62}
63
64impl<T> State<T>
65where
66    T: Clone + 'static,
67{
68    pub fn update_active_surface(
69        &mut self,
70        surface: &WlSurface,
71        x: f64,
72        y: f64,
73        dnd_state: Option<&DragOffer>,
74    ) {
75        let had_dest = self
76            .dnd_state
77            .active_surface
78            .as_ref()
79            .map(|(_, d)| d.as_ref().map(|d| d.id))
80            .unwrap_or_default();
81        self.dnd_state.active_surface =
82            self.dnd_state.destinations.get(&surface.id()).map(|(s, dests)| {
83                let Some((dest, mime, actions)) = dests.iter().find_map(|r| {
84                    let actions = dnd_state.as_ref().map(|s| {
85                        (
86                            s.source_actions.intersection(r.actions),
87                            s.source_actions.intersection(r.preferred),
88                        )
89                    });
90                    let mime = dnd_state.as_ref().and_then(|dnd_state| {
91                        r.mime_types.iter().find(|m| {
92                            dnd_state.with_mime_types(|mimes| mimes.iter().any(|a| a == m.as_ref()))
93                        })
94                    });
95
96                    (r.rectangle.contains(x, y)
97                        && (r.mime_types.is_empty() || mime.is_some())
98                        && (r.actions.is_all()
99                            || dnd_state
100                                .as_ref()
101                                .map(|dnd_state| dnd_state.source_actions.intersects(r.actions))
102                                .unwrap_or(true)))
103                    .then(|| (r.clone(), mime, actions))
104                }) else {
105                    if let Some(old_id) = had_dest {
106                        if let Some(dnd_state) = dnd_state.as_ref() {
107                            if let Some(tx) = self.dnd_state.sender.as_ref() {
108                                _ = tx.send(DndEvent::Offer(
109                                    Some(old_id),
110                                    super::OfferEvent::LeaveDestination,
111                                ));
112                            }
113                            dnd_state.set_actions(DndAction::empty(), DndAction::empty());
114                            dnd_state.accept_mime_type(self.dnd_state.accept_ctr, None);
115                            self.dnd_state.accept_ctr = self.dnd_state.accept_ctr.wrapping_add(1);
116                            self.dnd_state.selected_action = DndAction::empty();
117                            self.dnd_state.selected_mime = None;
118                        }
119                    }
120                    return (s.clone(), None);
121                };
122                if !had_dest.is_some_and(|old_id| old_id == dest.id) {
123                    if let (Some((action, preferred_action)), Some(mime_type), Some(dnd_state)) =
124                        (actions, mime, dnd_state.as_ref())
125                    {
126                        if let Some((tx, old_id)) = self.dnd_state.sender.as_ref().zip(had_dest) {
127                            _ = tx.send(DndEvent::Offer(
128                                Some(old_id),
129                                super::OfferEvent::LeaveDestination,
130                            ));
131                        }
132                        if let Some(tx) = self.dnd_state.sender.as_ref() {
133                            _ = tx.send(DndEvent::Offer(Some(dest.id), OfferEvent::Enter {
134                                x,
135                                y,
136                                surface: s.s.clone(),
137                                mime_types: dest.mime_types.clone(),
138                            }));
139
140                            _ = tx.send(DndEvent::Offer(
141                                Some(dest.id),
142                                OfferEvent::SelectedAction(self.dnd_state.selected_action),
143                            ));
144                        }
145                        dnd_state.set_actions(action, preferred_action);
146                        self.dnd_state.selected_mime = Some(mime_type.clone());
147                        dnd_state.accept_mime_type(
148                            self.dnd_state.accept_ctr,
149                            Some(mime_type.to_string()),
150                        );
151                        self.dnd_state.accept_ctr = self.dnd_state.accept_ctr.wrapping_add(1);
152                    }
153                }
154                (s.clone(), Some(dest))
155            });
156    }
157
158    fn cur_id(&self) -> Option<u128> {
159        self.dnd_state.active_surface.as_ref().and_then(|(_, rect)| rect.as_ref().map(|r| r.id))
160    }
161
162    pub(crate) fn offer_drop(&mut self, wl_data_device: &WlDataDevice) {
163        let Some(tx) = self.dnd_state.sender.as_ref() else {
164            return;
165        };
166        let id = self.cur_id();
167        _ = tx.send(DndEvent::Offer(id, super::OfferEvent::Drop));
168
169        let Some(data_device) = self
170            .seats
171            .iter()
172            .find_map(|(_, s)| s.data_device.as_ref().filter(|dev| dev.inner() == wl_data_device))
173        else {
174            return;
175        };
176        let Some(dnd_state) = data_device.data().drag_offer() else {
177            return;
178        };
179
180        if self.dnd_state.selected_action == DndAction::Ask {
181            _ = tx.send(DndEvent::Offer(
182                id,
183                super::OfferEvent::SelectedAction(self.dnd_state.selected_action),
184            ));
185            return;
186        } else if self.dnd_state.selected_action.is_empty() {
187            return;
188        }
189        let Some(mime) = self.dnd_state.selected_mime.take() else {
190            dnd_state.accept_mime_type(dnd_state.serial, None);
191            return;
192        };
193
194        dnd_state.set_actions(self.dnd_state.selected_action, self.dnd_state.selected_action);
195        dnd_state.accept_mime_type(dnd_state.serial, Some(mime.to_string()));
196
197        _ = self.load_dnd(mime, false);
198    }
199
200    pub(crate) fn offer_enter(
201        &mut self,
202        x: f64,
203        y: f64,
204        surface: &WlSurface,
205        wl_data_device: &WlDataDevice,
206    ) {
207        if self.dnd_state.sender.is_none() {
208            return;
209        }
210        let Some(data_device) = self
211            .seats
212            .iter()
213            .find_map(|(_, s)| s.data_device.as_ref().filter(|dev| dev.inner() == wl_data_device))
214        else {
215            return;
216        };
217        let drag_offer = data_device.data().drag_offer();
218        if drag_offer.is_none() && self.dnd_state.source_content.is_none() {
219            // Ignore cancelled internal DnD
220            return;
221        }
222        self.update_active_surface(surface, x, y, drag_offer.as_ref());
223        let Some((surface, id)) = self
224            .dnd_state
225            .active_surface
226            .as_ref()
227            .map(|(s, d)| (s.clone(), d.as_ref().map(|d| d.id)))
228        else {
229            return;
230        };
231        let Some(tx) = self.dnd_state.sender.as_ref() else {
232            return;
233        };
234        // TODO accept mime / action
235        _ = tx.send(DndEvent::Offer(id, super::OfferEvent::Enter {
236            x,
237            y,
238            surface: surface.s,
239            mime_types: Vec::new(),
240        }));
241    }
242
243    pub(crate) fn offer_motion(&mut self, x: f64, y: f64, wl_data_device: &WlDataDevice) {
244        let Some(surface) = self.dnd_state.active_surface.clone().map(|(s, _)| s) else {
245            return;
246        };
247        let Some(data_device) = self
248            .seats
249            .iter()
250            .find_map(|(_, s)| s.data_device.as_ref().filter(|dev| dev.inner() == wl_data_device))
251        else {
252            return;
253        };
254        let drag_offer = data_device.data().drag_offer();
255        if drag_offer.is_none() && self.dnd_state.source_content.is_none() {
256            // Ignore cancelled internal DnD
257            return;
258        }
259        self.update_active_surface(&surface.surface, x, y, drag_offer.as_ref());
260        let id = self.cur_id();
261        if let Some(tx) = self.dnd_state.sender.as_ref() {
262            _ = tx.send(DndEvent::Offer(id, super::OfferEvent::Motion { x, y }));
263        }
264    }
265
266    pub(crate) fn offer_leave(&mut self) {
267        if let Some(tx) = self.dnd_state.sender.as_ref() {
268            self.dnd_state.active_surface = None;
269            self.dnd_state.selected_action = DndAction::empty();
270            self.dnd_state.selected_mime = None;
271            _ = tx.send(DndEvent::Offer(None, super::OfferEvent::Leave))
272        }
273    }
274
275    pub(crate) fn handle_dnd_request(&mut self, r: DndRequest<T>) {
276        match r {
277            DndRequest::InitDnd(sender) => self.dnd_state.sender = Some(sender),
278            DndRequest::Surface(s, dests) => {
279                if dests.is_empty() {
280                    self.dnd_state.destinations.remove(&s.surface.id());
281                } else {
282                    self.dnd_state.destinations.insert(s.surface.id(), (s, dests));
283                }
284            },
285            DndRequest::StartDnd { internal, source, icon, content, actions } => {
286                _ = self.start_dnd(internal, source, icon, content, actions);
287            },
288            DndRequest::SetAction(a) => {
289                _ = self.user_selected_action(a);
290            },
291            DndRequest::DndEnd => {
292                if let Some(s) = self.dnd_state.icon_surface.take() {
293                    _ = s.destroy();
294                }
295                self.dnd_state.source_content = None;
296                self.dnd_state.dnd_source = None;
297                self.pool.remove(&0);
298            },
299            DndRequest::Peek(mime_type) => {
300                if let Err(err) = self.load_dnd(mime_type, true) {
301                    _ = self.reply_tx.send(Err(err));
302                }
303            },
304        };
305    }
306
307    fn start_dnd(
308        &mut self,
309        internal: bool,
310        source_surface: DndSurface<T>,
311        mut icon: Option<Icon<DndSurface<T>>>,
312        content: Box<dyn AsMimeTypes + Send>,
313        actions: DndAction,
314    ) -> std::io::Result<()> {
315        let latest = self
316            .latest_seat
317            .as_ref()
318            .ok_or_else(|| Error::new(ErrorKind::Other, "no events received on any seat"))?;
319        let seat = self
320            .seats
321            .get_mut(latest)
322            .ok_or_else(|| Error::new(ErrorKind::Other, "active seat lost"))?;
323        let serial = seat.latest_serial;
324
325        let data_device = seat
326            .data_device
327            .as_ref()
328            .ok_or_else(|| Error::new(ErrorKind::Other, "data device missing"))?;
329
330        let (icon_surface, buffer) = if let Some(i) = icon.take() {
331            match i {
332                Icon::Surface(s) => (Some(s.surface.clone()), None),
333                Icon::Buf { data, width, height, transparent } => {
334                    let surface = self.compositor_state.create_surface(&self.queue_handle);
335                    self.pool.remove(&0);
336                    let (_, wl_buffer, buf) = self
337                        .pool
338                        .create_buffer(
339                            width as i32,
340                            width as i32 * 4,
341                            height as i32,
342                            &0,
343                            if transparent { Format::Argb8888 } else { Format::Xrgb8888 },
344                        )
345                        .map_err(|err| Error::new(ErrorKind::Other, err))?;
346                    buf.copy_from_slice(&data);
347
348                    (Some(surface), Some((wl_buffer, width, height)))
349                },
350            }
351        } else {
352            (None, None)
353        };
354
355        if internal {
356            DragSource::start_internal_drag(
357                data_device,
358                &source_surface.surface,
359                icon_surface.as_ref(),
360                serial,
361            )
362        } else {
363            let mime_types = content.available();
364            let source = self
365                .data_device_manager_state
366                .as_ref()
367                .map(|s| {
368                    s.create_drag_and_drop_source(
369                        &self.queue_handle,
370                        mime_types.iter().map(|m| m.as_ref()),
371                        actions,
372                    )
373                })
374                .ok_or_else(|| Error::new(ErrorKind::Other, "data device manager missing"))?;
375            source.start_drag(data_device, &source_surface.surface, icon_surface.as_ref(), serial);
376
377            self.dnd_state.dnd_source = Some(source);
378            self.dnd_state.source_content = Some(content);
379            self.dnd_state.source_actions = actions;
380        }
381
382        if let (Some((wl_buffer, width, height)), Some(surface)) = (buffer, icon_surface) {
383            surface.damage_buffer(0, 0, width as i32, height as i32);
384            surface.attach(Some(wl_buffer), 0, 0);
385            surface.commit();
386
387            self.dnd_state.icon_surface = Some(surface);
388        }
389
390        Ok(())
391    }
392
393    fn user_selected_action(&mut self, a: DndAction) -> std::io::Result<()> {
394        let latest = self
395            .latest_seat
396            .as_ref()
397            .ok_or_else(|| Error::new(ErrorKind::Other, "no events received on any seat"))?;
398        let seat = self
399            .seats
400            .get_mut(latest)
401            .ok_or_else(|| Error::new(ErrorKind::Other, "active seat lost"))?;
402
403        let offer = seat
404            .data_device
405            .as_ref()
406            .and_then(|d| d.data().drag_offer())
407            .ok_or_else(|| Error::new(ErrorKind::Other, "offer does not exist."))?;
408        offer.set_actions(a, a);
409
410        if let Some(mime_type) = self.dnd_state.selected_mime.clone() {
411            _ = self.load_dnd(mime_type, false);
412        }
413        Ok(())
414    }
415
416    /// Load data for the given target.
417    pub fn load_dnd(&mut self, mut mime_type: MimeType, peek: bool) -> std::io::Result<()> {
418        let cur_id = self.cur_id();
419        let latest = self
420            .latest_seat
421            .as_ref()
422            .ok_or_else(|| Error::new(ErrorKind::Other, "no events received on any seat"))?;
423        let seat = self
424            .seats
425            .get_mut(latest)
426            .ok_or_else(|| Error::new(ErrorKind::Other, "active seat lost"))?;
427
428        let offer = seat
429            .data_device
430            .as_ref()
431            .and_then(|d| d.data().drag_offer())
432            .ok_or_else(|| Error::new(ErrorKind::Other, "offer does not exist."))?;
433
434        let read_pipe = { offer.receive(mime_type.to_string())? };
435
436        // Mark FD as non-blocking so we won't block ourselves.
437        unsafe {
438            set_non_blocking(read_pipe.as_raw_fd())?;
439        }
440
441        let mut reader_buffer = [0; 4096];
442        let mut content = Vec::new();
443        let _ = self.loop_handle.insert_source(read_pipe, move |_, file, state| {
444            let file = unsafe { file.get_mut() };
445
446            loop {
447                match file.read(&mut reader_buffer) {
448                    Ok(0) => {
449                        // only finish if not peeking
450                        if !peek {
451                            offer.finish();
452                        }
453
454                        if peek {
455                            _ = state
456                                .reply_tx
457                                .send(Ok((mem::take(&mut content), mem::take(&mut mime_type))));
458                        } else if let Some(tx) = state.dnd_state.sender.as_ref() {
459                            let _ = tx.send(DndEvent::Offer(cur_id, OfferEvent::Data {
460                                data: mem::take(&mut content),
461                                mime_type: mem::take(&mut mime_type),
462                            }));
463                        }
464                        break PostAction::Remove;
465                    },
466                    Ok(n) => content.extend_from_slice(&reader_buffer[..n]),
467                    Err(err) if err.kind() == ErrorKind::WouldBlock => break PostAction::Continue,
468                    Err(err) => {
469                        if peek {
470                            let _ = state.reply_tx.send(Err(err));
471                        }
472                        break PostAction::Remove;
473                    },
474                };
475            }
476        });
477
478        Ok(())
479    }
480
481    pub(crate) fn send_dnd_request(&self, write_pipe: WritePipe, mime: String) {
482        let Some(content) = self.dnd_state.source_content.as_ref() else {
483            return;
484        };
485        let Some(mime_type) = MimeType::find_allowed(&[mime], &content.available()) else {
486            return;
487        };
488
489        // Mark FD as non-blocking so we won't block ourselves.
490        unsafe {
491            if set_non_blocking(write_pipe.as_raw_fd()).is_err() {
492                return;
493            }
494        }
495
496        // Don't access the content on the state directly, since it could change during
497        // the send.
498        let contents = content.as_bytes(&mime_type);
499        let Some(contents) = contents else {
500            return;
501        };
502
503        let mut written = 0;
504        let _ = self.loop_handle.insert_source(write_pipe, move |_, file, _| {
505            let file = unsafe { file.get_mut() };
506            loop {
507                match file.write(&contents[written..]) {
508                    Ok(n) if written + n == contents.len() => {
509                        written += n;
510                        break PostAction::Remove;
511                    },
512                    Ok(n) => written += n,
513                    Err(err) if err.kind() == ErrorKind::WouldBlock => break PostAction::Continue,
514                    Err(_) => break PostAction::Remove,
515                }
516            }
517        });
518    }
519}