<NME> mod.rs
<BEF> //! 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 the freestanding [`set_data_device_focus`] function:
//!   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 additional mechanisms allowing your compositor to see and interact with
//! the contents of the data device:
//!
//! - the freestanding function [`set_data_device_selection`]
//!   allows you to set the contents of the selection for your clients
//! - the freestanding function [`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 defines the role `"dnd_icon"` that is assigned to surfaces used as drag'n'drop icons.
//!
//! - the freestanding function `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.
//!
//! ## Initialization
//!
//! ```
//! # extern crate wayland_server;
//! # #[macro_use] extern crate smithay;
//! use smithay::wayland::data_device::{init_data_device, default_action_chooser};
//!
//! # fn main(){
//! # let mut event_loop = wayland_server::calloop::EventLoop::<()>::new().unwrap();
//! # let mut display = wayland_server::Display::new(event_loop.handle());
//! // 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
//!     None                    // insert a logger here
//! );
//! # }
//! ```
//! impl ServerDndGrabHandler for State {}
//! impl DataDeviceHandler for State {
//!     fn data_device_state(&self) -> &DataDeviceState { &self.data_device_state }
//!     // ... override default implementations here to customize handling ...
//! }
    protocol::{
        wl_data_device,
        wl_data_device_manager::{self, DndAction},
        wl_data_offer, wl_data_source,
    },
    Client, Display, Global, NewResource, Resource,
};

use wayland::seat::Seat;

mod data_source;
mod dnd_grab;
    Client, DisplayHandle, GlobalDispatch,
};

use crate::{
    input::{
        pointer::{Focus, GrabStartData as PointerGrabStartData},
        Seat, SeatHandler,
    /// 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(Option<Resource<wl_data_source::WlDataSource>>),
    /// A client requested to read the server-set selection
    SendSelection {
        /// the requested mime type
    /// Action chooser for DnD negociation
    fn action_choice(&mut self, available: DndAction, preferred: DndAction) -> DndAction {
        default_action_chooser(available, preferred)
    },
}

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

/// Events that are generated during client initiated drag'n'drop
#[allow(unused_variables)]
pub trait ClientDndGrabHandler: SeatHandler + Sized {
    /// A client started a drag'n'drop as response to a user pointer action
    ///
    /// * `source` - 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 transfer by itself.
    /// * `icon` - The icon the client requested to be used to be associated with the cursor icon
    ///            during the drag'n'drop.
    /// * `seat` - The seat on which the DnD operation was started
    fn started(&mut self, source: Option<WlDataSource>, icon: Option<WlSurface>, seat: Seat<Self>) {}

    /// 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 generated for client-initiated drag'n'drop session.
    ///
    /// * `seat` - The seat on which the DnD action was finished.
    fn dropped(&mut self, seat: Seat<Self>) {}
}

/// Event generated by the interactions of clients with a server initiated drag'n'drop
#[allow(unused_variables)]
pub trait ServerDndGrabHandler {
    /// The client chose an action
    fn action(&mut self, action: DndAction) {}

    /// The DnD resource was dropped by the user
    ///
    /// After that, the client can still interact with your resource
    fn dropped(&mut self) {}

    /// The Dnd was cancelled
    ///
    /// The client can no longer interact
    fn cancelled(&mut self) {}

    /// The client requested for data to be sent
    ///
    /// * `mime_type` - The requested mime type
    /// * `fd` - The FD to write into
    fn send(&mut self, mime_type: String, fd: OwnedFd) {}

    /// The client has finished interacting with the resource
    ///
    /// This can only happen after the resource was dropped.
    fn finished(&mut self) {}
}

/// State of data device
#[derive(Debug)]
pub struct DataDeviceState {
    log: slog::Logger,
    manager_global: GlobalId,
}

impl DataDeviceState {
    /// Regiseter new [WlDataDeviceManager] global
    pub fn new<D, L>(display: &DisplayHandle, logger: L) -> Self
    where
        L: Into<Option<::slog::Logger>>,
        D: GlobalDispatch<WlDataDeviceManager, ()> + 'static,
        D: DataDeviceHandler,
    {
        let log = crate::slog_or_fallback(logger).new(slog::o!("smithay_module" => "data_device_mgr"));

        let manager_global = display.create_global::<D, WlDataDeviceManager, _>(3, ());

        Self { log, manager_global }
    }

    /// [WlDataDeviceManager] GlobalId getter
    pub fn global(&self) -> GlobalId {
        self.manager_global.clone()
    }
}

/// 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()
    }
}

/// Set the data device focus to a certain client for a given seat
pub fn set_data_device_focus<D>(dh: &DisplayHandle, seat: &Seat<D>, client: Option<Client>)
where
    D: SeatHandler + DataDeviceHandler + 'static,
{
    seat.user_data()
        .insert_if_missing(|| RefCell::new(SeatData::new()));
    let seat_data = seat.user_data().get::<RefCell<SeatData>>().unwrap();
    seat_data.borrow_mut().set_focus::<D>(dh, 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 [`DataDeviceHandler::send_selection`] event.
pub fn set_data_device_selection<D>(dh: &DisplayHandle, seat: &Seat<D>, mime_types: Vec<String>)
where
    D: SeatHandler + DataDeviceHandler + 'static,
{
    seat.user_data()
        .insert_if_missing(|| RefCell::new(SeatData::new()));
    let seat_data = seat.user_data().get::<RefCell<SeatData>>().unwrap();
    seat_data.borrow_mut().set_selection::<D>(
        dh,
        Selection::Compositor(SourceMetadata {
            mime_types,
            dnd_action: DndAction::empty(),
        }),
    );
}

/// Start a drag'n'drop from a resource controlled by the compositor
///
/// You'll receive events generated by the interaction of clients with your
/// drag'n'drop in the provided callback. See [`ServerDndGrabHandler`] for details about
/// which events can be generated and what response is expected from you to them.
pub fn start_dnd<D, C>(
    dh: &DisplayHandle,
    seat: &Seat<D>,
    data: &mut D,
    serial: Serial,
    start_data: PointerGrabStartData<D>,
    metadata: SourceMetadata,
) where
    D: SeatHandler + DataDeviceHandler + 'static,
    <D as SeatHandler>::PointerFocus: WaylandFocus,
{
    seat.user_data()
        .insert_if_missing(|| RefCell::new(SeatData::new()));
    if let Some(pointer) = seat.get_pointer() {
        pointer.set_grab(
            data,
            server_dnd_grab::ServerDnDGrab::new(dh, start_data, metadata, seat.clone()),
            serial,
/// 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()`.
pub fn init_data_device<F, C, L>(
    display: &mut Display,
    callback: C,
    action_choice: F,
    logger: L,
) -> Global<wl_data_device_manager::WlDataDeviceManager>
where
    F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static,
    C: FnMut(DataDeviceEvent) + Send + 'static,
    L: Into<Option<::slog::Logger>>,
{
    let log = ::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(), log.clone());
    });

    global
    {
        fn bind(
            _state: &mut D,
            _handle: &DisplayHandle,
            _client: &wayland_server::Client,
            resource: wayland_server::New<WlDataDeviceManager>,
            _global_data: &(),
            data_init: &mut wayland_server::DataInit<'_, D>,
        ) {
            data_init.init(resource, ());
        }
    }

    impl<D> Dispatch<WlDataDeviceManager, (), D> for DataDeviceState
    where
        D: Dispatch<WlDataDeviceManager, ()>,
        D: Dispatch<WlDataSource, DataSourceUserData>,
        D: Dispatch<WlDataDevice, DataDeviceUserData>,
        D: DataDeviceHandler,
        D: 'static,
    {
        fn request(
            state: &mut D,
            _client: &wayland_server::Client,
            _resource: &WlDataDeviceManager,
            request: wl_data_device_manager::Request,
            _data: &(),
            _dhandle: &DisplayHandle,
            data_init: &mut wayland_server::DataInit<'_, D>,
        ) {
            let data_device_state = state.data_device_state();

            match request {
                wl_data_device_manager::Request::CreateDataSource { id } => {
                    data_init.init(id, DataSourceUserData::new());
                }
                wl_data_device_manager::Request::GetDataDevice { id, seat: wl_seat } => {
                    match Seat::<D>::from_resource(&wl_seat) {
                        Some(seat) => {
                            seat.user_data()
                                .insert_if_missing(|| RefCell::new(SeatData::new()));

                            let data_device = data_init.init(id, DataDeviceUserData { wl_seat });

                            let seat_data = seat.user_data().get::<RefCell<SeatData>>().unwrap();
                            seat_data.borrow_mut().add_device(data_device);
                        }
                        None => {
                            error!(&data_device_state.log, "Unmanaged seat given to a data device.");
                        }
                    }
                }
                _ => unreachable!(),
            }
        }
    }
}

#[allow(missing_docs)] // TODO
#[macro_export]
macro_rules! delegate_data_device {
    ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
    }
}

fn implement_ddm<F, C>(
    new_ddm: NewResource<wl_data_device_manager::WlDataDeviceManager>,
    callback: Arc<Mutex<C>>,
    action_choice: Arc<Mutex<F>>,
    log: ::slog::Logger,
) -> Resource<wl_data_device_manager::WlDataDeviceManager>
where
    F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static,
    C: FnMut(DataDeviceEvent) + Send + 'static,
{
    use self::wl_data_device_manager::Request;
    new_ddm.implement(
                        seat.clone(),
                        callback.clone(),
                        action_choice.clone(),
                        log.clone(),
                    );
                    seat_data.lock().unwrap().known_devices.push(data_device);
    action_choice: Arc<Mutex<FnMut(DndAction, DndAction) -> DndAction + Send + 'static>>,
}

fn implement_data_device<F, C>(
    new_dd: NewResource<wl_data_device::WlDataDevice>,
    seat: Seat,
    callback: Arc<Mutex<C>>,
    action_choice: Arc<Mutex<F>>,
    log: ::slog::Logger,
) -> Resource<wl_data_device::WlDataDevice>
where
    F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static,
    C: FnMut(DataDeviceEvent) + Send + 'static,
{
    use self::wl_data_device::Request;
    let dd_data = DataDeviceData {
            Request::StartDrag {
                source,
                origin,
                icon: _,
                serial,
            } => {
                /* TODO: handle the icon */
                if let Some(pointer) = seat.get_pointer() {
                    if pointer.has_grab(serial) {
                        // The StartDrag is in response to a pointer implicit grab, all is good
                        (&mut *callback.lock().unwrap())(DataDeviceEvent::DnDStarted(source.clone()));
                        pointer.set_grab(dnd_grab::DnDGrab::new(source, origin, seat.clone()), serial);
                        return;
                    }
                }

<MSG> data_device: support for custom DnD icons

<DFF> @@ -24,23 +24,33 @@
 //! - the freestanding function `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};
+//! 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
+//!     &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
-//!     None                    // insert a logger here
+//!     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
 //! );
 //! # }
 //! ```
@@ -52,12 +62,15 @@ use wayland_server::{
     protocol::{
         wl_data_device,
         wl_data_device_manager::{self, DndAction},
-        wl_data_offer, wl_data_source,
+        wl_data_offer, wl_data_source, wl_surface,
     },
     Client, Display, Global, NewResource, Resource,
 };
 
-use wayland::seat::Seat;
+use wayland::{
+    compositor::{roles::Role, CompositorToken},
+    seat::Seat,
+};
 
 mod data_source;
 mod dnd_grab;
@@ -71,7 +84,22 @@ 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(Option<Resource<wl_data_source::WlDataSource>>),
+    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
@@ -81,6 +109,10 @@ pub enum DataDeviceEvent {
     },
 }
 
+/// The role applied to surfaces used as DnD icons
+#[derive(Default)]
+pub struct DnDIconRole;
+
 enum Selection {
     Empty,
     Client(Resource<wl_data_source::WlDataSource>),
@@ -246,22 +278,31 @@ impl SeatData {
 /// 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()`.
-pub fn init_data_device<F, C, L>(
+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 = ::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(), log.clone());
+        implement_ddm(
+            new_ddm,
+            callback.clone(),
+            action_choice.clone(),
+            token,
+            log.clone(),
+        );
     });
 
     global
@@ -330,15 +371,18 @@ where
     }
 }
 
-fn implement_ddm<F, C>(
+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(
@@ -357,6 +401,7 @@ where
                         seat.clone(),
                         callback.clone(),
                         action_choice.clone(),
+                        token.clone(),
                         log.clone(),
                     );
                     seat_data.lock().unwrap().known_devices.push(data_device);
@@ -376,16 +421,19 @@ struct DataDeviceData {
     action_choice: Arc<Mutex<FnMut(DndAction, DndAction) -> DndAction + Send + 'static>>,
 }
 
-fn implement_data_device<F, C>(
+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 {
@@ -397,15 +445,37 @@ where
             Request::StartDrag {
                 source,
                 origin,
-                icon: _,
+                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.clone()));
-                        pointer.set_grab(dnd_grab::DnDGrab::new(source, origin, seat.clone()), serial);
+                        (&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;
                     }
                 }

data_device: support for custom DnD icons