Skip to main content

smithay_client_toolkit/data_device_manager/
data_offer.rs

1use std::{
2    ops::{Deref, DerefMut},
3    os::unix::prelude::{AsFd, OwnedFd},
4    sync::{Arc, Mutex},
5};
6
7use log::warn;
8
9use crate::dispatch2::Dispatch2;
10use crate::reexports::client::{
11    protocol::{
12        wl_data_device_manager::DndAction,
13        wl_data_offer::{self, WlDataOffer},
14        wl_surface::WlSurface,
15    },
16    Connection, Proxy, QueueHandle,
17};
18
19use super::ReadPipe;
20
21/// Handler trait for DataOffer events.
22///
23/// The functions defined in this trait are called as DataOffer events are received from the compositor.
24pub trait DataOfferHandler: Sized {
25    /// Called to advertise the available DnD Actions as set by the source.
26    fn source_actions(
27        &mut self,
28        conn: &Connection,
29        qh: &QueueHandle<Self>,
30        offer: &mut DragOffer,
31        actions: DndAction,
32    );
33
34    /// Called to advertise the action selected by the compositor after matching
35    /// the source/destination side actions. Only one action or none will be
36    /// selected in the actions sent by the compositor. This may be called
37    /// multiple times during a DnD operation. The most recent DndAction is the
38    /// only valid one.
39    ///
40    /// At the time of a `drop` event on the data device, this action must be
41    /// used except in the case of an ask action. In the case that the last
42    /// action received is `ask`, the destination asks the user for their
43    /// preference, then calls set_actions & accept each one last time. Finally,
44    /// the destination may then request data to be sent and finishing the data
45    /// offer
46    fn selected_action(
47        &mut self,
48        conn: &Connection,
49        qh: &QueueHandle<Self>,
50        offer: &mut DragOffer,
51        actions: DndAction,
52    );
53}
54
55/// An error that may occur when working with data offers.
56#[derive(Debug, thiserror::Error)]
57pub enum DataOfferError {
58    #[error("offer is not valid to receive from yet")]
59    InvalidReceive,
60
61    #[error("IO error")]
62    Io(std::io::Error),
63}
64
65#[derive(Debug, Clone)]
66pub struct DragOffer {
67    /// the wl_data offer if it exists
68    pub(crate) data_offer: WlDataOffer,
69    /// the serial for this data offer's enter event
70    pub serial: u32,
71    /// the surface that this DnD is active on
72    pub surface: WlSurface,
73    /// the x position on the surface
74    pub x: f64,
75    /// the y position on this surface
76    pub y: f64,
77    /// the timestamp a motion event was received in millisecond granularity
78    pub time: Option<u32>,
79    /// the advertised drag actions
80    pub source_actions: DndAction,
81    /// the compositor selected drag action
82    pub selected_action: DndAction,
83    /// whether or not the drag has been dropped
84    pub dropped: bool,
85    /// whether or not the drag has left
86    pub left: bool,
87}
88
89impl DragOffer {
90    pub fn finish(&self) {
91        if self.data_offer.version() >= 3 {
92            self.data_offer.finish();
93        }
94    }
95
96    /// Inspect the mime types available on the given offer.
97    pub fn with_mime_types<T, F: Fn(&[String]) -> T>(&self, callback: F) -> T {
98        let mime_types =
99            &self.data_offer.data::<DataOfferData>().unwrap().inner.lock().unwrap().mime_types;
100        callback(mime_types)
101    }
102
103    /// Set the accepted and preferred drag and drop actions.
104    /// This request determines the final result of the drag-and-drop operation.
105    /// If the end result is that no action is accepted, the drag source will receive wl_data_source.cancelled.
106    pub fn set_actions(&self, actions: DndAction, preferred_action: DndAction) {
107        if self.data_offer.version() >= 3 && !self.left {
108            self.data_offer.set_actions(actions, preferred_action);
109        }
110    }
111
112    /// Receive data with the given mime type.
113    /// This request may happen multiple times for different mime types, both before and after wl_data_device.drop.
114    /// Drag-and-drop destination clients may preemptively fetch data or examine it more closely to determine acceptance.
115    pub fn receive(&self, mime_type: String) -> std::io::Result<ReadPipe> {
116        // When the data device has left, we can't receive unless it was previously dropped.
117        if !self.left || self.dropped {
118            receive(&self.data_offer, mime_type)
119        } else {
120            Err(std::io::Error::other("offer has left"))
121        }
122    }
123
124    /// Accept the given mime type, or None to reject the offer.
125    /// In version 2, this request is used for feedback, but doesn't affect the final result of the drag-and-drop operation.
126    /// In version 3, this request determines the final result of the drag-and-drop operation.
127    pub fn accept_mime_type(&self, serial: u32, mime_type: Option<String>) {
128        if !self.left {
129            self.data_offer.accept(serial, mime_type);
130        }
131    }
132
133    /// Destroy the data offer.
134    pub fn destroy(&self) {
135        self.data_offer.destroy();
136    }
137
138    /// Retrieve a reference to the inner wl_data_offer.
139    pub fn inner(&self) -> &WlDataOffer {
140        &self.data_offer
141    }
142}
143
144impl PartialEq for DragOffer {
145    fn eq(&self, other: &Self) -> bool {
146        self.data_offer == other.data_offer
147    }
148}
149
150#[derive(Debug, Clone)]
151pub struct SelectionOffer {
152    /// the wl_data offer
153    pub(crate) data_offer: WlDataOffer,
154}
155
156impl SelectionOffer {
157    /// Inspect the mime types available on the given offer.
158    pub fn with_mime_types<T, F: Fn(&[String]) -> T>(&self, callback: F) -> T {
159        let mime_types =
160            &self.data_offer.data::<DataOfferData>().unwrap().inner.lock().unwrap().mime_types;
161        callback(mime_types)
162    }
163
164    pub fn receive(&self, mime_type: String) -> Result<ReadPipe, DataOfferError> {
165        receive(&self.data_offer, mime_type).map_err(DataOfferError::Io)
166    }
167
168    pub fn destroy(&self) {
169        self.data_offer.destroy();
170    }
171
172    pub fn inner(&self) -> &WlDataOffer {
173        &self.data_offer
174    }
175}
176
177impl PartialEq for SelectionOffer {
178    fn eq(&self, other: &Self) -> bool {
179        self.data_offer == other.data_offer
180    }
181}
182
183#[derive(Debug, Default)]
184pub struct DataOfferData {
185    pub(crate) inner: Arc<Mutex<DataDeviceOfferInner>>,
186}
187
188impl DataOfferData {
189    /// Inspect the mime types available on the given offer.
190    pub fn with_mime_types<T, F: Fn(&[String]) -> T>(&self, callback: F) -> T {
191        let mime_types = &self.inner.lock().unwrap().mime_types;
192        callback(mime_types)
193    }
194
195    pub(crate) fn push_mime_type(&self, mime_type: String) {
196        self.inner.lock().unwrap().mime_types.push(mime_type);
197    }
198
199    pub(crate) fn set_source_action(&self, action: DndAction) {
200        let mut inner = self.inner.lock().unwrap();
201        match &mut inner.deref_mut().offer {
202            DataDeviceOffer::Drag(ref mut o) => o.source_actions = action,
203            DataDeviceOffer::Selection(_) => {}
204            DataDeviceOffer::Undetermined(ref mut o) => o.actions = action,
205        };
206    }
207
208    pub(crate) fn set_selected_action(&self, action: DndAction) {
209        let mut inner = self.inner.lock().unwrap();
210        match &mut inner.deref_mut().offer {
211            DataDeviceOffer::Drag(ref mut o) => o.selected_action = action,
212            DataDeviceOffer::Selection(_) => {}    // error?
213            DataDeviceOffer::Undetermined(_) => {} // error?
214        };
215    }
216
217    pub(crate) fn to_selection_offer(&self) {
218        let mut inner = self.inner.lock().unwrap();
219        match &mut inner.deref_mut().offer {
220            DataDeviceOffer::Drag(o) => {
221                inner.offer =
222                    DataDeviceOffer::Selection(SelectionOffer { data_offer: o.data_offer.clone() });
223            }
224            DataDeviceOffer::Selection(_) => {}
225            DataDeviceOffer::Undetermined(o) => {
226                inner.offer = DataDeviceOffer::Selection(SelectionOffer {
227                    data_offer: o.data_offer.clone().unwrap(),
228                });
229            }
230        }
231    }
232
233    pub(crate) fn init_undetermined_offer(&self, offer: &WlDataOffer) {
234        let mut inner = self.inner.lock().unwrap();
235        match &mut inner.deref_mut().offer {
236            DataDeviceOffer::Drag(o) => {
237                inner.offer = DataDeviceOffer::Undetermined(UndeterminedOffer {
238                    data_offer: Some(offer.clone()),
239                    actions: o.source_actions,
240                });
241            }
242            DataDeviceOffer::Selection(_) => {
243                inner.offer = DataDeviceOffer::Undetermined(UndeterminedOffer {
244                    data_offer: Some(offer.clone()),
245                    actions: DndAction::empty(),
246                });
247            }
248            DataDeviceOffer::Undetermined(o) => {
249                o.data_offer = Some(offer.clone());
250            }
251        }
252    }
253
254    pub(crate) fn to_dnd_offer(
255        &self,
256        serial: u32,
257        surface: WlSurface,
258        x: f64,
259        y: f64,
260        time: Option<u32>,
261    ) {
262        let mut inner = self.inner.lock().unwrap();
263        match &mut inner.deref_mut().offer {
264            DataDeviceOffer::Drag(_) => {}
265            DataDeviceOffer::Selection(o) => {
266                inner.offer = DataDeviceOffer::Drag(DragOffer {
267                    data_offer: o.data_offer.clone(),
268                    source_actions: DndAction::empty(),
269                    selected_action: DndAction::empty(),
270                    serial,
271                    surface,
272                    x,
273                    y,
274                    time,
275                    dropped: false,
276                    left: false,
277                });
278            }
279            DataDeviceOffer::Undetermined(o) => {
280                inner.offer = DataDeviceOffer::Drag(DragOffer {
281                    data_offer: o.data_offer.clone().unwrap(),
282                    source_actions: o.actions,
283                    selected_action: DndAction::empty(),
284                    serial,
285                    surface,
286                    x,
287                    y,
288                    time,
289                    dropped: false,
290                    left: false,
291                });
292            }
293        }
294    }
295
296    pub(crate) fn motion(&self, x: f64, y: f64, time: u32) {
297        let mut inner = self.inner.lock().unwrap();
298        match &mut inner.deref_mut().offer {
299            DataDeviceOffer::Drag(o) => {
300                o.x = x;
301                o.y = y;
302                o.time = Some(time);
303            }
304            DataDeviceOffer::Selection(_) => {}
305            DataDeviceOffer::Undetermined(_) => {}
306        }
307    }
308
309    pub(crate) fn as_drag_offer(&self) -> Option<DragOffer> {
310        match &self.inner.lock().unwrap().deref().offer {
311            DataDeviceOffer::Drag(o) => Some(o.clone()),
312            _ => None,
313        }
314    }
315
316    pub(crate) fn leave(&self) -> bool {
317        let mut inner = self.inner.lock().unwrap();
318        match &mut inner.deref_mut().offer {
319            DataDeviceOffer::Drag(o) => {
320                o.left = true;
321                if !o.dropped {
322                    o.data_offer.destroy();
323                }
324                !o.dropped
325            }
326            _ => {
327                warn!("DataDeviceOffer::leave called on non-drag offer");
328                false
329            }
330        }
331    }
332
333    pub(crate) fn as_selection_offer(&self) -> Option<SelectionOffer> {
334        match &self.inner.lock().unwrap().deref().offer {
335            DataDeviceOffer::Selection(o) => Some(o.clone()),
336            _ => None,
337        }
338    }
339}
340
341#[derive(Debug, Default)]
342pub struct DataDeviceOfferInner {
343    pub(crate) offer: DataDeviceOffer,
344    pub(crate) mime_types: Vec<String>,
345}
346
347#[derive(Debug, Clone, PartialEq)]
348pub(crate) enum DataDeviceOffer {
349    Drag(DragOffer),
350    Selection(SelectionOffer),
351    Undetermined(UndeterminedOffer),
352}
353
354impl Default for DataDeviceOffer {
355    fn default() -> Self {
356        DataDeviceOffer::Undetermined(UndeterminedOffer {
357            data_offer: None,
358            actions: DndAction::empty(),
359        })
360    }
361}
362
363impl<D> Dispatch2<wl_data_offer::WlDataOffer, D> for DataOfferData
364where
365    D: DataOfferHandler,
366{
367    fn event(
368        &self,
369        state: &mut D,
370        _offer: &wl_data_offer::WlDataOffer,
371        event: <wl_data_offer::WlDataOffer as wayland_client::Proxy>::Event,
372        conn: &wayland_client::Connection,
373        qh: &wayland_client::QueueHandle<D>,
374    ) {
375        match event {
376            wl_data_offer::Event::Offer { mime_type } => {
377                self.push_mime_type(mime_type);
378            }
379            wl_data_offer::Event::SourceActions { source_actions } => {
380                match source_actions {
381                    wayland_client::WEnum::Value(a) => {
382                        self.set_source_action(a);
383                        match &mut self.inner.lock().unwrap().offer {
384                            DataDeviceOffer::Drag(o) => {
385                                state.source_actions(conn, qh, o, a);
386                            }
387                            DataDeviceOffer::Selection(_) => {}
388                            DataDeviceOffer::Undetermined(_) => {}
389                        }
390                    }
391                    wayland_client::WEnum::Unknown(_) => {} // Ignore
392                }
393            }
394            wl_data_offer::Event::Action { dnd_action } => {
395                match dnd_action {
396                    wayland_client::WEnum::Value(a) => {
397                        self.set_selected_action(a);
398                        match &mut self.inner.lock().unwrap().offer {
399                            DataDeviceOffer::Drag(o) => {
400                                state.selected_action(conn, qh, o, a);
401                            }
402                            DataDeviceOffer::Selection(_) => {}
403                            DataDeviceOffer::Undetermined(_) => {}
404                        }
405                    }
406                    wayland_client::WEnum::Unknown(_) => {} // Ignore
407                }
408            }
409            _ => unimplemented!(),
410        };
411    }
412}
413
414#[derive(Debug, Clone)]
415pub(crate) struct UndeterminedOffer {
416    pub(crate) data_offer: Option<WlDataOffer>,
417    pub actions: DndAction,
418}
419
420impl PartialEq for UndeterminedOffer {
421    fn eq(&self, other: &Self) -> bool {
422        self.data_offer == other.data_offer
423    }
424}
425
426/// Request to receive the data of a given mime type.
427///
428/// You can do this several times, as a reaction to motion of
429/// the dnd cursor, or to inspect the data in order to choose your
430/// response.
431///
432/// Note that you should *not* read the contents right away in a
433/// blocking way, as you may deadlock your application doing so.
434/// At least make sure you flush your events to the server before
435/// doing so.
436///
437/// Fails if too many file descriptors were already open and a pipe
438/// could not be created.
439pub fn receive(offer: &WlDataOffer, mime_type: String) -> std::io::Result<ReadPipe> {
440    use rustix::pipe::{pipe_with, PipeFlags};
441    // create a pipe
442    let (readfd, writefd) = pipe_with(PipeFlags::CLOEXEC)?;
443
444    receive_to_fd(offer, mime_type, writefd);
445
446    Ok(ReadPipe::from(readfd))
447}
448
449/// Receive data to the write end of a raw file descriptor. If you have the read end, you can read from it.
450///
451/// You can do this several times, as a reaction to motion of
452/// the dnd cursor, or to inspect the data in order to choose your
453/// response.
454///
455/// Note that you should *not* read the contents right away in a
456/// blocking way, as you may deadlock your application doing so.
457/// At least make sure you flush your events to the server before
458/// doing so.
459///
460/// The provided file destructor must be a valid FD for writing, and will be closed
461/// once the contents are written.
462pub fn receive_to_fd(offer: &WlDataOffer, mime_type: String, writefd: OwnedFd) {
463    offer.receive(mime_type, writefd.as_fd());
464}