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