use std::{ cell::RefCell, fmt, os::unix::io::{AsFd, OwnedFd}, sync::{Arc, Mutex}, }; use wayland_server::{ backend::{protocol::Message, ClientId, Handle, ObjectData, ObjectId}, protocol::{ wl_data_device_manager::DndAction, wl_data_offer::{self, WlDataOffer}, wl_data_source::{self, WlDataSource}, wl_surface::WlSurface, }, DisplayHandle, Resource, }; use crate::{ input::{ pointer::{ AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle, RelativeMotionEvent, }, touch::{GrabStartData as TouchGrabStartData, TouchGrab}, Seat, SeatHandler, }, utils::{IsAlive, Logical, Point, Serial, SERIAL_COUNTER}, wayland::{seat::WaylandFocus, selection::seat_data::SeatData}, }; use super::{with_source_metadata, ClientDndGrabHandler, DataDeviceHandler}; /// Grab during a client-initiated DnD operation. pub struct DnDGrab { dh: DisplayHandle, pointer_start_data: Option>, touch_start_data: Option>, data_source: Option, current_focus: Option, pending_offers: Vec, offer_data: Option>>, icon: Option, origin: WlSurface, seat: Seat, } impl fmt::Debug for DnDGrab { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("DnDGrab") .field("dh", &self.dh) .field("pointer_start_data", &self.pointer_start_data) .field("touch_start_data", &self.touch_start_data) .field("data_source", &self.data_source) .field("current_focus", &self.current_focus) .field("pending_offers", &self.pending_offers) .field("offer_data", &self.offer_data) .field("icon", &self.icon) .field("origin", &self.origin) .field("seat", &self.seat) .finish() } } impl DnDGrab { pub(crate) fn new_pointer( dh: &DisplayHandle, start_data: PointerGrabStartData, source: Option, origin: WlSurface, seat: Seat, icon: Option, ) -> Self { Self { dh: dh.clone(), pointer_start_data: Some(start_data), touch_start_data: None, data_source: source, current_focus: None, pending_offers: Vec::with_capacity(1), offer_data: None, origin, icon, seat, } } pub(crate) fn new_touch( dh: &DisplayHandle, start_data: TouchGrabStartData, source: Option, origin: WlSurface, seat: Seat, icon: Option, ) -> Self { Self { dh: dh.clone(), pointer_start_data: None, touch_start_data: Some(start_data), data_source: source, current_focus: None, pending_offers: Vec::with_capacity(1), offer_data: None, origin, icon, seat, } } } impl DnDGrab where D: DataDeviceHandler, D: SeatHandler, D: 'static, { fn update_focus( &mut self, focus: Option<(F, Point)>, location: Point, serial: Serial, time: u32, ) { let seat_data = self .seat .user_data() .get::>>() .unwrap() .borrow_mut(); if focus.as_ref().and_then(|(s, _)| s.wl_surface()).as_deref() != self.current_focus.as_ref() { // focus changed, we need to make a leave if appropriate if let Some(surface) = self.current_focus.take() { // only leave if there is a data source or we are on the original client if self.data_source.is_some() || self.origin.id().same_client_as(&surface.id()) { for device in seat_data.known_data_devices() { if device.id().same_client_as(&surface.id()) { device.leave(); } } // disable the offers self.pending_offers.clear(); if let Some(offer_data) = self.offer_data.take() { offer_data.lock().unwrap().active = false; } } } } if let Some((surface, surface_location)) = focus .as_ref() .and_then(|(h, loc)| h.wl_surface().map(|s| (s, loc))) { // early return if the surface is no longer valid let client = match self.dh.get_client(surface.id()) { Ok(c) => c, Err(_) => return, }; let (x, y) = (location - *surface_location).into(); if self.current_focus.is_none() { // We entered a new surface, send the data offer if appropriate if let Some(ref source) = self.data_source { let offer_data = Arc::new(Mutex::new(OfferData { active: true, dropped: false, accepted: true, finished: false, chosen_action: DndAction::empty(), })); for device in seat_data .known_data_devices() .filter(|d| d.id().same_client_as(&surface.id())) { let handle = self.dh.backend_handle(); // create a data offer let offer = handle .create_object::( client.id(), WlDataOffer::interface(), device.version(), Arc::new(DndDataOffer { offer_data: offer_data.clone(), source: source.clone(), }), ) .unwrap(); let offer = WlDataOffer::from_id(&self.dh, offer).unwrap(); // advertize the offer to the client device.data_offer(&offer); with_source_metadata(source, |meta| { for mime_type in meta.mime_types.iter().cloned() { offer.offer(mime_type); } offer.source_actions(meta.dnd_action); }) .unwrap(); device.enter(serial.into(), &surface, x, y, Some(&offer)); self.pending_offers.push(offer); } self.offer_data = Some(offer_data); } else { // only send if we are on a surface of the same client if self.origin.id().same_client_as(&surface.id()) { for device in seat_data.known_data_devices() { if device.id().same_client_as(&surface.id()) { device.enter(serial.into(), &surface, x, y, None); } } } } self.current_focus = Some(surface.into_owned()); } else { // make a move if self.data_source.is_some() || self.origin.id().same_client_as(&surface.id()) { for device in seat_data.known_data_devices() { if device.id().same_client_as(&surface.id()) { device.motion(time, x, y); } } } } } } fn drop(&mut self, data: &mut D) { // the user dropped, proceed to the drop let seat_data = self .seat .user_data() .get::>>() .unwrap() .borrow_mut(); let validated = if let Some(ref data) = self.offer_data { let data = data.lock().unwrap(); data.accepted && (!data.chosen_action.is_empty()) } else { false }; if let Some(ref surface) = self.current_focus { if self.data_source.is_some() || self.origin.id().same_client_as(&surface.id()) { for device in seat_data.known_data_devices() { if device.id().same_client_as(&surface.id()) && validated { device.drop(); } } } } if let Some(ref offer_data) = self.offer_data { let mut data = offer_data.lock().unwrap(); if validated { data.dropped = true; } else { data.active = false; } } if let Some(ref source) = self.data_source { if !validated { source.cancelled(); } else if source.version() >= wl_data_source::EVT_DND_DROP_PERFORMED_SINCE { source.dnd_drop_performed(); } } ClientDndGrabHandler::dropped(data, self.current_focus.clone(), validated, self.seat.clone()); self.icon = None; // in all cases abandon the drop // no more buttons are pressed, release the grab if let Some(ref surface) = self.current_focus { for device in seat_data.known_data_devices() { if device.id().same_client_as(&surface.id()) { device.leave(); } } } } } impl PointerGrab for DnDGrab where D: DataDeviceHandler, D: SeatHandler, ::PointerFocus: WaylandFocus, D: 'static, { fn motion( &mut self, data: &mut D, handle: &mut PointerInnerHandle<'_, D>, focus: Option<(::PointerFocus, Point)>, event: &MotionEvent, ) { // While the grab is active, no client has pointer focus handle.motion(data, None, event); self.update_focus(focus, event.location, event.serial, event.time); } fn relative_motion( &mut self, data: &mut D, handle: &mut PointerInnerHandle<'_, D>, focus: Option<(::PointerFocus, Point)>, event: &RelativeMotionEvent, ) { handle.relative_motion(data, focus, event); } fn button(&mut self, data: &mut D, handle: &mut PointerInnerHandle<'_, D>, event: &ButtonEvent) { if handle.current_pressed().is_empty() { // the user dropped, proceed to the drop handle.unset_grab(self, data, event.serial, event.time, true); } } fn axis(&mut self, data: &mut D, handle: &mut PointerInnerHandle<'_, D>, details: AxisFrame) { // we just forward the axis events as is handle.axis(data, details); } fn frame(&mut self, data: &mut D, handle: &mut PointerInnerHandle<'_, D>) { handle.frame(data); } fn gesture_swipe_begin( &mut self, data: &mut D, handle: &mut PointerInnerHandle<'_, D>, event: &GestureSwipeBeginEvent, ) { handle.gesture_swipe_begin(data, event); } fn gesture_swipe_update( &mut self, data: &mut D, handle: &mut PointerInnerHandle<'_, D>, event: &GestureSwipeUpdateEvent, ) { handle.gesture_swipe_update(data, event); } fn gesture_swipe_end( &mut self, data: &mut D, handle: &mut PointerInnerHandle<'_, D>, event: &GestureSwipeEndEvent, ) { handle.gesture_swipe_end(data, event); } fn gesture_pinch_begin( &mut self, data: &mut D, handle: &mut PointerInnerHandle<'_, D>, event: &GesturePinchBeginEvent, ) { handle.gesture_pinch_begin(data, event); } fn gesture_pinch_update( &mut self, data: &mut D, handle: &mut PointerInnerHandle<'_, D>, event: &GesturePinchUpdateEvent, ) { handle.gesture_pinch_update(data, event); } fn gesture_pinch_end( &mut self, data: &mut D, handle: &mut PointerInnerHandle<'_, D>, event: &GesturePinchEndEvent, ) { handle.gesture_pinch_end(data, event); } fn gesture_hold_begin( &mut self, data: &mut D, handle: &mut PointerInnerHandle<'_, D>, event: &GestureHoldBeginEvent, ) { handle.gesture_hold_begin(data, event); } fn gesture_hold_end( &mut self, data: &mut D, handle: &mut PointerInnerHandle<'_, D>, event: &GestureHoldEndEvent, ) { handle.gesture_hold_end(data, event); } fn start_data(&self) -> &PointerGrabStartData { self.pointer_start_data.as_ref().unwrap() } fn unset(&mut self, data: &mut D) { self.drop(data); } } impl TouchGrab for DnDGrab where D: DataDeviceHandler, D: SeatHandler, ::TouchFocus: WaylandFocus, D: 'static, { fn down( &mut self, _data: &mut D, _handle: &mut crate::input::touch::TouchInnerHandle<'_, D>, _focus: Option<(::TouchFocus, Point)>, _event: &crate::input::touch::DownEvent, _seq: crate::utils::Serial, ) { // Ignore } fn up( &mut self, data: &mut D, handle: &mut crate::input::touch::TouchInnerHandle<'_, D>, event: &crate::input::touch::UpEvent, _seq: crate::utils::Serial, ) { if event.slot != self.start_data().slot { return; } handle.unset_grab(self, data); } fn motion( &mut self, _data: &mut D, _handle: &mut crate::input::touch::TouchInnerHandle<'_, D>, focus: Option<(::TouchFocus, Point)>, event: &crate::input::touch::MotionEvent, _seq: crate::utils::Serial, ) { if event.slot != self.start_data().slot { return; } self.update_focus(focus, event.location, SERIAL_COUNTER.next_serial(), event.time); } fn frame( &mut self, _data: &mut D, _handle: &mut crate::input::touch::TouchInnerHandle<'_, D>, _seq: crate::utils::Serial, ) { } fn cancel( &mut self, data: &mut D, handle: &mut crate::input::touch::TouchInnerHandle<'_, D>, _seq: crate::utils::Serial, ) { // TODO: should we cancel something here? handle.unset_grab(self, data); } fn shape( &mut self, _data: &mut D, _handle: &mut crate::input::touch::TouchInnerHandle<'_, D>, _event: &crate::input::touch::ShapeEvent, _seq: Serial, ) { } fn orientation( &mut self, _data: &mut D, _handle: &mut crate::input::touch::TouchInnerHandle<'_, D>, _event: &crate::input::touch::OrientationEvent, _seq: Serial, ) { } fn start_data(&self) -> &TouchGrabStartData { self.touch_start_data.as_ref().unwrap() } fn unset(&mut self, data: &mut D) { self.drop(data); } } #[derive(Debug)] struct OfferData { active: bool, dropped: bool, accepted: bool, finished: bool, chosen_action: DndAction, } #[derive(Debug)] struct DndDataOffer { offer_data: Arc>, source: WlDataSource, } impl ObjectData for DndDataOffer where D: DataDeviceHandler, D: 'static, { fn request( self: Arc, dh: &Handle, handler: &mut D, _client_id: ClientId, msg: Message, ) -> Option>> { let dh = DisplayHandle::from(dh.clone()); if let Ok((resource, request)) = WlDataOffer::parse_request(&dh, msg) { handle_dnd(handler, &resource, request, &self); } None } fn destroyed( self: Arc, _handle: &Handle, _data: &mut D, _client_id: ClientId, _object_id: ObjectId, ) { } } fn handle_dnd(handler: &mut D, offer: &WlDataOffer, request: wl_data_offer::Request, data: &DndDataOffer) where D: DataDeviceHandler, D: 'static, { use self::wl_data_offer::Request; let source = &data.source; let mut data = data.offer_data.lock().unwrap(); match request { Request::Accept { mime_type, .. } => { if let Some(mtype) = mime_type { if let Err(crate::utils::UnmanagedResource) = with_source_metadata(source, |meta| { data.accepted = meta.mime_types.contains(&mtype); }) { data.accepted = false; } } else { data.accepted = false; } } Request::Receive { mime_type, fd } => { // 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.alive() && data.active; if valid { source.send(mime_type, fd.as_fd()); } } Request::Destroy => { if source.version() >= 3 && data.dropped && !data.finished { source.cancelled(); } } Request::Finish => { if !data.active { offer.post_error( wl_data_offer::Error::InvalidFinish, "Cannot finish a data offer that is no longer active.", ); return; } if !data.accepted { offer.post_error( wl_data_offer::Error::InvalidFinish, "Cannot finish a data offer that has not been accepted.", ); return; } if !data.dropped { offer.post_error( wl_data_offer::Error::InvalidFinish, "Cannot finish a data offer that has not been dropped.", ); return; } if data.chosen_action.is_empty() { offer.post_error( wl_data_offer::Error::InvalidFinish, "Cannot finish a data offer with no valid action.", ); return; } source.dnd_finished(); data.active = false; data.finished = true; } Request::SetActions { dnd_actions, preferred_action, } => { let dnd_actions = dnd_actions.into_result().unwrap_or(DndAction::None); let preferred_action = preferred_action.into_result().unwrap_or(DndAction::None); // preferred_action must only contain one bitflag at the same time if ![DndAction::None, DndAction::Move, DndAction::Copy, DndAction::Ask] .contains(&preferred_action) { offer.post_error(wl_data_offer::Error::InvalidAction, "Invalid preferred action."); return; } let source_actions = with_source_metadata(source, |meta| meta.dnd_action).unwrap_or_else(|_| DndAction::empty()); let possible_actions = source_actions & dnd_actions; let chosen_action = handler.action_choice(possible_actions, preferred_action); // check that the user provided callback respects that one precise action should be chosen debug_assert!( [DndAction::None, DndAction::Move, DndAction::Copy, DndAction::Ask].contains(&chosen_action), "Only one precise action should be chosen" ); if chosen_action != data.chosen_action { data.chosen_action = chosen_action; offer.action(chosen_action); source.action(chosen_action); } } _ => unreachable!(), } }