From 280decf863dca185a033d14c047df0aa7e554bac Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Thu, 22 Nov 2018 16:02:01 +0100 Subject: [PATCH] data_device: support for custom DnD icons --- anvil/src/shell.rs | 2 + anvil/src/udev.rs | 1 + anvil/src/winit.rs | 8 ++- src/wayland/data_device/dnd_grab.rs | 30 +++++++-- src/wayland/data_device/mod.rs | 100 +++++++++++++++++++++++----- 5 files changed, 119 insertions(+), 22 deletions(-) diff --git a/anvil/src/shell.rs b/anvil/src/shell.rs index c2955d7..3ec7c5f 100644 --- a/anvil/src/shell.rs +++ b/anvil/src/shell.rs @@ -9,6 +9,7 @@ use rand; use smithay::{ wayland::{ compositor::{compositor_init, CompositorToken, SurfaceAttributes, SurfaceEvent}, + data_device::DnDIconRole, seat::CursorImageRole, shell::{ legacy::{ @@ -31,6 +32,7 @@ use window_map::{Kind as SurfaceKind, WindowMap}; define_roles!(Roles => [ XdgSurface, XdgSurfaceRole ] [ ShellSurface, ShellSurfaceRole<()>] + [ DnDIcon, DnDIconRole ] [ CursorImage, CursorImageRole ] ); diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index 0d9bac1..ca605ab 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -140,6 +140,7 @@ pub fn run_udev(mut display: Display, mut event_loop: EventLoop<()>, log: Logger &mut display.borrow_mut(), |_| {}, default_action_chooser, + compositor_token.clone(), log.clone(), ); diff --git a/anvil/src/winit.rs b/anvil/src/winit.rs index a299e4b..f071db8 100644 --- a/anvil/src/winit.rs +++ b/anvil/src/winit.rs @@ -50,7 +50,13 @@ pub fn run_winit(display: &mut Display, event_loop: &mut EventLoop<()>, log: Log let (compositor_token, _, _, window_map) = init_shell(display, log.clone()); - init_data_device(display, |_| {}, default_action_chooser, log.clone()); + init_data_device( + display, + |_| {}, + default_action_chooser, + compositor_token.clone(), + log.clone(), + ); let (mut seat, _) = Seat::new(display, "winit".into(), compositor_token.clone(), log.clone()); diff --git a/src/wayland/data_device/dnd_grab.rs b/src/wayland/data_device/dnd_grab.rs index 132219f..b72449e 100644 --- a/src/wayland/data_device/dnd_grab.rs +++ b/src/wayland/data_device/dnd_grab.rs @@ -8,37 +8,49 @@ use wayland_server::{ NewResource, Resource, }; -use wayland::seat::{AxisFrame, PointerGrab, PointerInnerHandle, Seat}; +use wayland::{ + compositor::{roles::Role, CompositorToken}, + seat::{AxisFrame, PointerGrab, PointerInnerHandle, Seat}, +}; -use super::{with_source_metadata, DataDeviceData, SeatData}; +use super::{with_source_metadata, DataDeviceData, DnDIconRole, SeatData}; -pub(crate) struct DnDGrab { +pub(crate) struct DnDGrab { data_source: Option>, current_focus: Option>, pending_offers: Vec>, offer_data: Option>>, + icon: Option>, origin: Resource, + callback: Arc>, + token: CompositorToken, seat: Seat, } -impl DnDGrab { +impl + 'static> DnDGrab { pub(crate) fn new( source: Option>, origin: Resource, seat: Seat, - ) -> DnDGrab { + icon: Option>, + token: CompositorToken, + callback: Arc>, + ) -> DnDGrab { DnDGrab { data_source: source, current_focus: None, pending_offers: Vec::with_capacity(1), offer_data: None, origin, + icon, + callback, + token, seat, } } } -impl PointerGrab for DnDGrab { +impl + 'static> PointerGrab for DnDGrab { fn motion( &mut self, _handle: &mut PointerInnerHandle, @@ -215,6 +227,12 @@ impl PointerGrab for DnDGrab { source.send(wl_data_source::Event::Cancelled); } } + (&mut *self.callback.lock().unwrap())(super::DataDeviceEvent::DnDDropped); + if let Some(icon) = self.icon.take() { + if icon.is_alive() { + self.token.remove_role::(&icon).unwrap(); + } + } // in all cases abandon the drop // no more buttons are pressed, release the grab handle.unset_grab(serial, time); diff --git a/src/wayland/data_device/mod.rs b/src/wayland/data_device/mod.rs index e1d410e..1ed1e50 100644 --- a/src/wayland/data_device/mod.rs +++ b/src/wayland/data_device/mod.rs @@ -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>), /// A client started a drag'n'drop as response to a user pointer action - DnDStarted(Option>), + 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>, + /// The icon the client requested to be used to be associated with the cursor icon + /// during the drag'n'drop. + icon: Option>, + }, + /// 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), @@ -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( +pub fn init_data_device( display: &mut Display, callback: C, action_choice: F, + token: CompositorToken, logger: L, ) -> Global where F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static, C: FnMut(DataDeviceEvent) + Send + 'static, + R: Role + 'static, + U: 'static, L: Into>, { 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( +fn implement_ddm( new_ddm: NewResource, callback: Arc>, action_choice: Arc>, + token: CompositorToken, log: ::slog::Logger, ) -> Resource where F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static, C: FnMut(DataDeviceEvent) + Send + 'static, + R: Role + '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 DndAction + Send + 'static>>, } -fn implement_data_device( +fn implement_data_device( new_dd: NewResource, seat: Seat, callback: Arc>, action_choice: Arc>, + token: CompositorToken, log: ::slog::Logger, ) -> Resource where F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static, C: FnMut(DataDeviceEvent) + Send + 'static, + R: Role + '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::(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; } }