1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
//! Utilities for manipulating the data devices
//!
//! The data device is wayland's abstraction to represent both selection (copy/paste) and
//! drag'n'drop actions. This module provides logic to handle this part of the protocol.
//! Selection and drag'n'drop are per-seat notions.
//!
//! This module provides 2 main freestanding functions:
//!
//! - [`init_data_device`](::wayland::data_device::init_data_device): this function must be called
//!   during the compositor startup to initialize the data device logic
//! - [`set_data_device_focus`](::wayland::data_device::set_data_device_focus): this function sets
//!   the data device focus for a given seat; you'd typically call it whenever the keyboard focus
//!   changes, to follow it (for example in the focus hook of your keyboards)
//!
//! Using these two functions is enough for your clients to be able to interact with each other using
//! the data devices.
//!
//! The module also provides additionnal mechanisms allowing your compositor to see and interact with
//! the contents of the data device:
//!
//! - You can provide a callback closure to [`init_data_device`](::wayland::data_device::init_data_device)
//!   to peek into the the actions of your clients
//! - the freestanding function [`set_data_device_selection`](::wayland::data_device::set_data_device_selection)
//!   allows you to set the contents of the selection for your clients
//! - the freestanding function [`start_dnd`](::wayland::data_device::start_dnd) allows you to initiate a drag'n'drop event from the compositor
//!   itself and receive interactions of clients with it via an other dedicated callback.
//!
//! The module also defines the `DnDIconRole` that you need to insert into your compositor roles enum, to
//! represent surfaces that are used as a DnD icon.
//!
//! ## Initialization
//!
//! ```
//! # extern crate wayland_server;
//! # #[macro_use] extern crate smithay;
//! use smithay::wayland::data_device::{init_data_device, default_action_chooser, DnDIconRole};
//! # use smithay::wayland::compositor::compositor_init;
//!
//! // You need to insert the `DndIconRole` into your roles, to handle requests from clients
//! // to set a surface as a dnd icon
//! define_roles!(Roles => [DnDIcon, DnDIconRole]);
//!
//! # fn main(){
//! # let mut event_loop = wayland_server::calloop::EventLoop::<()>::new().unwrap();
//! # let mut display = wayland_server::Display::new(event_loop.handle());
//! # let (compositor_token, _, _) = compositor_init::<(), Roles, _, _>(&mut display, |_, _, _| {}, None);
//! // init the data device:
//! init_data_device(
//!     &mut display,            // the display
//!     |dnd_event| { /* a callback to react to client DnD/selection actions */ },
//!     default_action_chooser,  // a closure to choose the DnD action depending on clients
//!                              // negociation
//!     compositor_token.clone(), // a compositor token
//!     None                     // insert a logger here
//! );
//! # }
//! ```

use std::os::unix::io::RawFd;
use std::sync::{Arc, Mutex};

use wayland_server::{
    protocol::{
        wl_data_device,
        wl_data_device_manager::{self, DndAction},
        wl_data_offer, wl_data_source, wl_surface,
    },
    Client, Display, Global, NewResource, Resource,
};

use crate::wayland::{
    compositor::{roles::Role, CompositorToken},
    seat::Seat,
};

mod data_source;
mod dnd_grab;
mod server_dnd_grab;

pub use self::data_source::{with_source_metadata, SourceMetadata};
pub use self::server_dnd_grab::ServerDndEvent;

/// Events that are generated by interactions of the clients with the data device
pub enum DataDeviceEvent {
    /// A client has set the selection
    NewSelection(Option<Resource<wl_data_source::WlDataSource>>),
    /// A client started a drag'n'drop as response to a user pointer action
    DnDStarted {
        /// The data source provided by the client
        ///
        /// If it is `None`, this means the DnD is restricted to surfaces of the
        /// same client and the client will manage data transfert by itself.
        source: Option<Resource<wl_data_source::WlDataSource>>,
        /// The icon the client requested to be used to be associated with the cursor icon
        /// during the drag'n'drop.
        icon: Option<Resource<wl_surface::WlSurface>>,
    },
    /// The drag'n'drop action was finished by the user releasing the buttons
    ///
    /// At this point, any pointer icon should be removed.
    ///
    /// Note that this event will only be genrated for client-initiated drag'n'drop session.
    DnDDropped,
    /// A client requested to read the server-set selection
    SendSelection {
        /// the requested mime type
        mime_type: String,
        /// the fd to write into
        fd: RawFd,
    },
}

/// The role applied to surfaces used as DnD icons
#[derive(Default)]
pub struct DnDIconRole;

enum Selection {
    Empty,
    Client(Resource<wl_data_source::WlDataSource>),
    Compositor(SourceMetadata),
}

struct SeatData {
    known_devices: Vec<Resource<wl_data_device::WlDataDevice>>,
    selection: Selection,
    log: ::slog::Logger,
    current_focus: Option<Client>,
}

impl SeatData {
    fn set_selection(&mut self, new_selection: Selection) {
        self.selection = new_selection;
        self.send_selection();
    }

    fn set_focus(&mut self, new_focus: Option<Client>) {
        self.current_focus = new_focus;
        self.send_selection();
    }

    fn send_selection(&mut self) {
        let client = match self.current_focus.as_ref() {
            Some(c) => c,
            None => return,
        };
        // first sanitize the selection, reseting it to null if the client holding
        // it dropped it
        let cleanup = if let Selection::Client(ref data_source) = self.selection {
            !data_source.is_alive()
        } else {
            false
        };
        if cleanup {
            self.selection = Selection::Empty;
        }
        // then send it if appropriate
        match self.selection {
            Selection::Empty => {
                // send an empty selection
                for dd in &self.known_devices {
                    // skip data devices not belonging to our client
                    if dd.client().map(|c| !c.equals(client)).unwrap_or(true) {
                        continue;
                    }
                    dd.send(wl_data_device::Event::Selection { id: None });
                }
            }
            Selection::Client(ref data_source) => {
                for dd in &self.known_devices {
                    // skip data devices not belonging to our client
                    if dd.client().map(|c| !c.equals(client)).unwrap_or(true) {
                        continue;
                    }
                    let source = data_source.clone();
                    let log = self.log.clone();
                    // create a corresponding data offer
                    let offer = client
                        .create_resource::<wl_data_offer::WlDataOffer>(dd.version())
                        .unwrap()
                        .implement(
                            move |req, _offer| match req {
                                wl_data_offer::Request::Receive { fd, mime_type } => {
                                    // check if the source and associated mime type is still valid
                                    let valid = with_source_metadata(&source, |meta| {
                                        meta.mime_types.contains(&mime_type)
                                    })
                                    .unwrap_or(false)
                                        && source.is_alive();
                                    if !valid {
                                        // deny the receive
                                        debug!(log, "Denying a wl_data_offer.receive with invalid source.");
                                    } else {
                                        source.send(wl_data_source::Event::Send { mime_type, fd });
                                    }
                                    let _ = ::nix::unistd::close(fd);
                                }
                                _ => { /* seleciton data offers only care about the `receive` event */ }
                            },
                            None::<fn(_)>,
                            (),
                        );
                    // advertize the offer to the client
                    dd.send(wl_data_device::Event::DataOffer { id: offer.clone() });
                    with_source_metadata(data_source, |meta| {
                        for mime_type in meta.mime_types.iter().cloned() {
                            offer.send(wl_data_offer::Event::Offer { mime_type })
                        }
                    })
                    .unwrap();
                    dd.send(wl_data_device::Event::Selection { id: Some(offer) });
                }
            }
            Selection::Compositor(ref meta) => {
                for dd in &self.known_devices {
                    // skip data devices not belonging to our client
                    if dd.client().map(|c| !c.equals(client)).unwrap_or(true) {
                        continue;
                    }
                    let log = self.log.clone();
                    let offer_meta = meta.clone();
                    let callback = dd.user_data::<DataDeviceData>().unwrap().callback.clone();
                    // create a corresponding data offer
                    let offer = client
                        .create_resource::<wl_data_offer::WlDataOffer>(dd.version())
                        .unwrap()
                        .implement(
                            move |req, _offer| match req {
                                wl_data_offer::Request::Receive { fd, mime_type } => {
                                    // check if the associated mime type is valid
                                    if !offer_meta.mime_types.contains(&mime_type) {
                                        // deny the receive
                                        debug!(log, "Denying a wl_data_offer.receive with invalid source.");
                                        let _ = ::nix::unistd::close(fd);
                                    } else {
                                        (&mut *callback.lock().unwrap())(DataDeviceEvent::SendSelection {
                                            mime_type,
                                            fd,
                                        });
                                    }
                                }
                                _ => { /* seleciton data offers only care about the `receive` event */ }
                            },
                            None::<fn(_)>,
                            (),
                        );
                    // advertize the offer to the client
                    dd.send(wl_data_device::Event::DataOffer { id: offer.clone() });
                    for mime_type in meta.mime_types.iter().cloned() {
                        offer.send(wl_data_offer::Event::Offer { mime_type })
                    }
                    dd.send(wl_data_device::Event::Selection { id: Some(offer) });
                }
            }
        }
    }
}

impl SeatData {
    fn new(log: ::slog::Logger) -> SeatData {
        SeatData {
            known_devices: Vec::new(),
            selection: Selection::Empty,
            log,
            current_focus: None,
        }
    }
}

/// Initialize the data device global
///
/// You can provide a callback to peek into the actions of your clients over the data devices
/// (allowing you to retrieve the current selection buffer, or intercept DnD data). See the
/// [`DataDeviceEvent`] type for details about what notifications you can receive. Note that this
/// closure will not receive notifications about dnd actions the compositor initiated, see
/// [`start_dnd`] for details about that.
///
/// You also need to provide a `(DndAction, DndAction) -> DndAction` closure that will arbitrate
/// the choice of action resulting from a drag'n'drop session. Its first argument is the set of
/// available actions (which is the intersection of the actions supported by the source and targets)
/// and the second argument is the preferred action reported by the target. If no action should be
/// chosen (and thus the drag'n'drop should abort on drop), return
/// [`DndAction::empty()`](wayland_server::protocol::wl_data_device_manager::DndAction::empty).
pub fn init_data_device<F, C, U, R, L>(
    display: &mut Display,
    callback: C,
    action_choice: F,
    token: CompositorToken<U, R>,
    logger: L,
) -> Global<wl_data_device_manager::WlDataDeviceManager>
where
    F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static,
    C: FnMut(DataDeviceEvent) + Send + 'static,
    R: Role<DnDIconRole> + 'static,
    U: 'static,
    L: Into<Option<::slog::Logger>>,
{
    let log = crate::slog_or_stdlog(logger).new(o!("smithay_module" => "data_device_mgr"));
    let action_choice = Arc::new(Mutex::new(action_choice));
    let callback = Arc::new(Mutex::new(callback));
    let global = display.create_global(3, move |new_ddm, _version| {
        implement_ddm(
            new_ddm,
            callback.clone(),
            action_choice.clone(),
            token,
            log.clone(),
        );
    });

    global
}

/// Set the data device focus to a certain client for a given seat
pub fn set_data_device_focus(seat: &Seat, client: Option<Client>) {
    // ensure the seat user_data is ready
    // TODO: find a better way to retrieve a logger without requiring the user
    // to provide one ?
    // This should be a rare path anyway, it is unlikely that a client gets focus
    // before initializing its data device, which would already init the user_data.
    seat.user_data().insert_if_missing(|| {
        Mutex::new(SeatData::new(
            seat.arc.log.new(o!("smithay_module" => "data_device_mgr")),
        ))
    });
    let seat_data = seat.user_data().get::<Mutex<SeatData>>().unwrap();
    seat_data.lock().unwrap().set_focus(client);
}

/// Set a compositor-provided selection for this seat
///
/// You need to provide the available mime types for this selection.
///
/// Whenever a client requests to read the selection, your callback will
/// receive a [`DataDeviceEvent::SendSelection`] event.
pub fn set_data_device_selection(seat: &Seat, mime_types: Vec<String>) {
    // TODO: same question as in set_data_device_focus
    seat.user_data().insert_if_missing(|| {
        Mutex::new(SeatData::new(
            seat.arc.log.new(o!("smithay_module" => "data_device_mgr")),
        ))
    });
    let seat_data = seat.user_data().get::<Mutex<SeatData>>().unwrap();
    seat_data
        .lock()
        .unwrap()
        .set_selection(Selection::Compositor(SourceMetadata {
            mime_types,
            dnd_action: DndAction::empty(),
        }));
}

/// Start a drag'n'drop from a ressource controlled by the compositor
///
/// You'll receive events generated by the interaction of clients with your
/// drag'n'drop in the provided callback. See [`ServerDndEvent`] for details about
/// which events can be generated and what response is expected from you to them.
pub fn start_dnd<C>(seat: &Seat, serial: u32, metadata: SourceMetadata, callback: C)
where
    C: FnMut(ServerDndEvent) + Send + 'static,
{
    // TODO: same question as in set_data_device_focus
    seat.user_data().insert_if_missing(|| {
        Mutex::new(SeatData::new(
            seat.arc.log.new(o!("smithay_module" => "data_device_mgr")),
        ))
    });
    if let Some(pointer) = seat.get_pointer() {
        pointer.set_grab(
            server_dnd_grab::ServerDnDGrab::new(metadata, seat.clone(), Arc::new(Mutex::new(callback))),
            serial,
        );
        return;
    }
}

fn implement_ddm<F, C, U, R>(
    new_ddm: NewResource<wl_data_device_manager::WlDataDeviceManager>,
    callback: Arc<Mutex<C>>,
    action_choice: Arc<Mutex<F>>,
    token: CompositorToken<U, R>,
    log: ::slog::Logger,
) -> Resource<wl_data_device_manager::WlDataDeviceManager>
where
    F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static,
    C: FnMut(DataDeviceEvent) + Send + 'static,
    R: Role<DnDIconRole> + 'static,
    U: 'static,
{
    use self::wl_data_device_manager::Request;
    new_ddm.implement(
        move |req, _ddm| match req {
            Request::CreateDataSource { id } => {
                self::data_source::implement_data_source(id);
            }
            Request::GetDataDevice { id, seat } => match Seat::from_resource(&seat) {
                Some(seat) => {
                    // ensure the seat user_data is ready
                    seat.user_data()
                        .insert_if_missing(|| Mutex::new(SeatData::new(log.clone())));
                    let seat_data = seat.user_data().get::<Mutex<SeatData>>().unwrap();
                    let data_device = implement_data_device(
                        id,
                        seat.clone(),
                        callback.clone(),
                        action_choice.clone(),
                        token.clone(),
                        log.clone(),
                    );
                    seat_data.lock().unwrap().known_devices.push(data_device);
                }
                None => {
                    error!(log, "Unmanaged seat given to a data device.");
                }
            },
        },
        None::<fn(_)>,
        (),
    )
}

struct DataDeviceData {
    callback: Arc<Mutex<dyn FnMut(DataDeviceEvent) + Send + 'static>>,
    action_choice: Arc<Mutex<dyn FnMut(DndAction, DndAction) -> DndAction + Send + 'static>>,
}

fn implement_data_device<F, C, U, R>(
    new_dd: NewResource<wl_data_device::WlDataDevice>,
    seat: Seat,
    callback: Arc<Mutex<C>>,
    action_choice: Arc<Mutex<F>>,
    token: CompositorToken<U, R>,
    log: ::slog::Logger,
) -> Resource<wl_data_device::WlDataDevice>
where
    F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static,
    C: FnMut(DataDeviceEvent) + Send + 'static,
    R: Role<DnDIconRole> + 'static,
    U: 'static,
{
    use self::wl_data_device::Request;
    let dd_data = DataDeviceData {
        callback: callback.clone(),
        action_choice,
    };
    new_dd.implement(
        move |req, dd| match req {
            Request::StartDrag {
                source,
                origin,
                icon,
                serial,
            } => {
                /* TODO: handle the icon */
                if let Some(pointer) = seat.get_pointer() {
                    if pointer.has_grab(serial) {
                        if let Some(ref icon) = icon {
                            if token.give_role::<DnDIconRole>(icon).is_err() {
                                dd.post_error(
                                    wl_data_device::Error::Role as u32,
                                    "Given surface already has an other role".into(),
                                );
                                return;
                            }
                        }
                        // The StartDrag is in response to a pointer implicit grab, all is good
                        (&mut *callback.lock().unwrap())(DataDeviceEvent::DnDStarted {
                            source: source.clone(),
                            icon: icon.clone(),
                        });
                        pointer.set_grab(
                            dnd_grab::DnDGrab::new(
                                source,
                                origin,
                                seat.clone(),
                                icon.clone(),
                                token.clone(),
                                callback.clone(),
                            ),
                            serial,
                        );
                        return;
                    }
                }
                debug!(log, "denying drag from client without implicit grab");
            }
            Request::SetSelection { source, serial: _ } => {
                if let Some(keyboard) = seat.get_keyboard() {
                    if dd
                        .client()
                        .as_ref()
                        .map(|c| keyboard.has_focus(c))
                        .unwrap_or(false)
                    {
                        let seat_data = seat.user_data().get::<Mutex<SeatData>>().unwrap();
                        (&mut *callback.lock().unwrap())(DataDeviceEvent::NewSelection(source.clone()));
                        // The client has kbd focus, it can set the selection
                        seat_data
                            .lock()
                            .unwrap()
                            .set_selection(source.map(Selection::Client).unwrap_or(Selection::Empty));
                        return;
                    }
                }
                debug!(log, "denying setting selection by a non-focused client");
            }
            Request::Release => {
                // Clean up the known devices
                seat.user_data()
                    .get::<Mutex<SeatData>>()
                    .unwrap()
                    .lock()
                    .unwrap()
                    .known_devices
                    .retain(|ndd| ndd.is_alive() && (!ndd.equals(&dd)))
            }
        },
        None::<fn(_)>,
        dd_data,
    )
}

/// A simple action chooser for DnD negociation
///
/// If the preferred action is available, it'll pick it. Otherwise, it'll pick the first
/// available in the following order: Ask, Copy, Move.
pub fn default_action_chooser(available: DndAction, preferred: DndAction) -> DndAction {
    // if the preferred action is valid (a single action) and in the available actions, use it
    // otherwise, follow a fallback stategy
    if [DndAction::Move, DndAction::Copy, DndAction::Ask].contains(&preferred)
        && available.contains(preferred)
    {
        preferred
    } else if available.contains(DndAction::Ask) {
        DndAction::Ask
    } else if available.contains(DndAction::Copy) {
        DndAction::Copy
    } else if available.contains(DndAction::Move) {
        DndAction::Move
    } else {
        DndAction::empty()
    }
}