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};
pub struct DnDGrab<D: SeatHandler> {
dh: DisplayHandle,
pointer_start_data: Option<PointerGrabStartData<D>>,
touch_start_data: Option<TouchGrabStartData<D>>,
data_source: Option<wl_data_source::WlDataSource>,
current_focus: Option<WlSurface>,
pending_offers: Vec<wl_data_offer::WlDataOffer>,
offer_data: Option<Arc<Mutex<OfferData>>>,
icon: Option<WlSurface>,
origin: WlSurface,
seat: Seat<D>,
}
impl<D: SeatHandler + 'static> fmt::Debug for DnDGrab<D> {
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<D: SeatHandler> DnDGrab<D> {
pub(crate) fn new_pointer(
dh: &DisplayHandle,
start_data: PointerGrabStartData<D>,
source: Option<wl_data_source::WlDataSource>,
origin: WlSurface,
seat: Seat<D>,
icon: Option<WlSurface>,
) -> 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<D>,
source: Option<wl_data_source::WlDataSource>,
origin: WlSurface,
seat: Seat<D>,
icon: Option<WlSurface>,
) -> 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<D> DnDGrab<D>
where
D: DataDeviceHandler,
D: SeatHandler,
D: 'static,
{
fn update_focus<F: WaylandFocus>(
&mut self,
focus: Option<(F, Point<f64, Logical>)>,
location: Point<f64, Logical>,
serial: Serial,
time: u32,
) {
let seat_data = self
.seat
.user_data()
.get::<RefCell<SeatData<D::SelectionUserData>>>()
.unwrap()
.borrow_mut();
if focus.as_ref().and_then(|(s, _)| s.wl_surface()).as_deref() != self.current_focus.as_ref() {
if let Some(surface) = self.current_focus.take() {
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();
}
}
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)))
{
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() {
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();
let offer = handle
.create_object::<D>(
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();
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 {
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 {
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) {
let seat_data = self
.seat
.user_data()
.get::<RefCell<SeatData<D::SelectionUserData>>>()
.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;
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<D> PointerGrab<D> for DnDGrab<D>
where
D: DataDeviceHandler,
D: SeatHandler,
<D as SeatHandler>::PointerFocus: WaylandFocus,
D: 'static,
{
fn motion(
&mut self,
data: &mut D,
handle: &mut PointerInnerHandle<'_, D>,
focus: Option<(<D as SeatHandler>::PointerFocus, Point<f64, Logical>)>,
event: &MotionEvent,
) {
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<(<D as SeatHandler>::PointerFocus, Point<f64, Logical>)>,
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() {
handle.unset_grab(self, data, event.serial, event.time, true);
}
}
fn axis(&mut self, data: &mut D, handle: &mut PointerInnerHandle<'_, D>, details: AxisFrame) {
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<D> {
self.pointer_start_data.as_ref().unwrap()
}
fn unset(&mut self, data: &mut D) {
self.drop(data);
}
}
impl<D> TouchGrab<D> for DnDGrab<D>
where
D: DataDeviceHandler,
D: SeatHandler,
<D as SeatHandler>::TouchFocus: WaylandFocus,
D: 'static,
{
fn down(
&mut self,
_data: &mut D,
_handle: &mut crate::input::touch::TouchInnerHandle<'_, D>,
_focus: Option<(<D as SeatHandler>::TouchFocus, Point<f64, Logical>)>,
_event: &crate::input::touch::DownEvent,
_seq: crate::utils::Serial,
) {
}
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<(<D as SeatHandler>::TouchFocus, Point<f64, Logical>)>,
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,
) {
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<D> {
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<Mutex<OfferData>>,
source: WlDataSource,
}
impl<D> ObjectData<D> for DndDataOffer
where
D: DataDeviceHandler,
D: 'static,
{
fn request(
self: Arc<Self>,
dh: &Handle,
handler: &mut D,
_client_id: ClientId,
msg: Message<ObjectId, OwnedFd>,
) -> Option<Arc<dyn ObjectData<D>>> {
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<Self>,
_handle: &Handle,
_data: &mut D,
_client_id: ClientId,
_object_id: ObjectId,
) {
}
}
fn handle_dnd<D>(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 } => {
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);
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);
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!(),
}
}