diff --git a/anvil/src/main.rs b/anvil/src/main.rs index ce4523e..ee1c95f 100644 --- a/anvil/src/main.rs +++ b/anvil/src/main.rs @@ -16,6 +16,7 @@ use smithay::wayland_server::{calloop::EventLoop, Display}; mod shaders; mod glium_drawer; mod input_handler; +mod misc; #[cfg(feature = "tty_launch")] mod raw_drm; mod shell; diff --git a/anvil/src/misc.rs b/anvil/src/misc.rs new file mode 100644 index 0000000..5d26ee6 --- /dev/null +++ b/anvil/src/misc.rs @@ -0,0 +1,19 @@ +use smithay::wayland_server::protocol::wl_data_device_manager::DndAction; + +pub fn dnd_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() + } +} diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index 8328c40..39d69f5 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -108,7 +108,7 @@ pub fn run_udev(mut display: Display, mut event_loop: EventLoop<()>, log: Logger let udev_session_id = notifier.register(&mut udev_backend); - init_data_device(&mut display.borrow_mut(), log.clone()); + init_data_device(&mut display.borrow_mut(), ::misc::dnd_action_chooser, log.clone()); let (mut w_seat, _) = Seat::new(&mut display.borrow_mut(), session.seat(), log.clone()); diff --git a/anvil/src/winit.rs b/anvil/src/winit.rs index 8890e88..ecb3ff4 100644 --- a/anvil/src/winit.rs +++ b/anvil/src/winit.rs @@ -54,7 +54,7 @@ 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, log.clone()); + init_data_device(display, ::misc::dnd_action_chooser, log.clone()); let (mut seat, _) = Seat::new(display, "winit".into(), log.clone()); diff --git a/src/wayland/data_device/dnd_grab.rs b/src/wayland/data_device/dnd_grab.rs index 3a6bb0f..9cdc683 100644 --- a/src/wayland/data_device/dnd_grab.rs +++ b/src/wayland/data_device/dnd_grab.rs @@ -12,21 +12,23 @@ use wayland::seat::{AxisFrame, PointerGrab, PointerInnerHandle, Seat}; use super::{with_source_metadata, SeatData}; -pub(crate) struct DnDGrab { +pub(crate) struct DnDGrab { data_source: Option>, current_focus: Option>, pending_offers: Vec>, offer_data: Option>>, origin: Resource, seat: Seat, + action_choice: Arc>, } -impl DnDGrab { +impl DnDGrab { pub(crate) fn new( source: Option>, origin: Resource, seat: Seat, - ) -> DnDGrab { + action_choice: Arc>, + ) -> DnDGrab { DnDGrab { data_source: source, current_focus: None, @@ -34,11 +36,15 @@ impl DnDGrab { offer_data: None, origin, seat, + action_choice, } } } -impl PointerGrab for DnDGrab { +impl PointerGrab for DnDGrab +where + F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static, +{ fn motion( &mut self, _handle: &mut PointerInnerHandle, @@ -96,8 +102,14 @@ impl PointerGrab for DnDGrab { // create a data offer let offer = client .create_resource::(device.version()) - .map(|offer| implement_dnd_data_offer(offer, source.clone(), offer_data.clone())) - .unwrap(); + .map(|offer| { + implement_dnd_data_offer( + offer, + source.clone(), + offer_data.clone(), + self.action_choice.clone(), + ) + }).unwrap(); // advertize the offer to the client device.send(wl_data_device::Event::DataOffer { id: offer.clone() }); with_source_metadata(source, |meta| { @@ -221,11 +233,15 @@ struct OfferData { chosen_action: DndAction, } -fn implement_dnd_data_offer( +fn implement_dnd_data_offer( offer: NewResource, source: Resource, offer_data: Arc>, -) -> Resource { + action_choice: Arc>, +) -> Resource +where + F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static, +{ use self::wl_data_offer::Request; offer.implement( move |req, offer| { @@ -296,20 +312,12 @@ fn implement_dnd_data_offer( let source_actions = with_source_metadata(&source, |meta| meta.dnd_action).unwrap_or(DndAction::empty()); let possible_actions = source_actions & DndAction::from_bits_truncate(dnd_actions); - if possible_actions.contains(preferred_action) { - // chose this one - data.chosen_action = preferred_action; - } else { - if possible_actions.contains(DndAction::Ask) { - data.chosen_action = DndAction::Ask; - } else if possible_actions.contains(DndAction::Copy) { - data.chosen_action = DndAction::Copy; - } else if possible_actions.contains(DndAction::Move) { - data.chosen_action = DndAction::Move; - } else { - data.chosen_action = DndAction::None; - } - } + data.chosen_action = + (&mut *action_choice.lock().unwrap())(possible_actions, preferred_action); + // check that the user provided callback respects that one precise action should be chosen + debug_assert!( + [DndAction::Move, DndAction::Copy, DndAction::Ask].contains(&data.chosen_action) + ); offer.send(wl_data_offer::Event::Action { dnd_action: data.chosen_action.to_raw(), }); diff --git a/src/wayland/data_device/mod.rs b/src/wayland/data_device/mod.rs index afa03ed..7a15564 100644 --- a/src/wayland/data_device/mod.rs +++ b/src/wayland/data_device/mod.rs @@ -1,7 +1,11 @@ -use std::sync::Mutex; +use std::sync::{Arc, Mutex}; use wayland_server::{ - protocol::{wl_data_device, wl_data_device_manager, wl_data_offer, wl_data_source}, + protocol::{ + wl_data_device, + wl_data_device_manager::{self, DndAction}, + wl_data_offer, wl_data_source, + }, Client, Display, Global, NewResource, Resource, }; @@ -121,17 +125,25 @@ impl SeatData { } /// Initialize the data device global -pub fn init_data_device( +/// +/// You 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()`. +pub fn init_data_device( display: &mut Display, + action_choice: F, logger: L, ) -> Global where + F: FnMut(DndAction, DndAction) -> DndAction + Send + '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 global = display.create_global(3, move |new_ddm, _version| { - implement_ddm(new_ddm, log.clone()); + implement_ddm(new_ddm, action_choice.clone(), log.clone()); }); global @@ -153,10 +165,14 @@ pub fn set_data_device_focus(seat: &Seat, client: Option) { seat_data.lock().unwrap().set_focus(client); } -fn implement_ddm( +fn implement_ddm( new_ddm: NewResource, + action_choice: Arc>, log: ::slog::Logger, -) -> Resource { +) -> Resource +where + F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static, +{ use self::wl_data_device_manager::Request; new_ddm.implement( move |req, _ddm| match req { @@ -169,7 +185,8 @@ fn implement_ddm( seat.user_data() .insert_if_missing(|| Mutex::new(SeatData::new(log.clone()))); let seat_data = seat.user_data().get::>().unwrap(); - let data_device = implement_data_device(id, seat.clone(), log.clone()); + let data_device = + implement_data_device(id, seat.clone(), action_choice.clone(), log.clone()); seat_data.lock().unwrap().known_devices.push(data_device); } None => { @@ -182,11 +199,15 @@ fn implement_ddm( ) } -fn implement_data_device( +fn implement_data_device( new_dd: NewResource, seat: Seat, + action_choice: Arc>, log: ::slog::Logger, -) -> Resource { +) -> Resource +where + F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static, +{ use self::wl_data_device::Request; new_dd.implement( move |req, dd| match req { @@ -200,7 +221,10 @@ fn implement_data_device( 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 - pointer.set_grab(dnd_grab::DnDGrab::new(source, origin, seat.clone()), serial); + pointer.set_grab( + dnd_grab::DnDGrab::new(source, origin, seat.clone(), action_choice.clone()), + serial, + ); return; } }