data_device: let the compositor generate DnD grabs

This commit is contained in:
Victor Berger 2018-11-21 23:40:25 +01:00
parent d41517f85b
commit 41f1b37834
3 changed files with 373 additions and 30 deletions

View File

@ -10,25 +10,23 @@ use wayland_server::{
use wayland::seat::{AxisFrame, PointerGrab, PointerInnerHandle, Seat}; use wayland::seat::{AxisFrame, PointerGrab, PointerInnerHandle, Seat};
use super::{with_source_metadata, SeatData}; use super::{with_source_metadata, DataDeviceData, SeatData};
pub(crate) struct DnDGrab<F: 'static> { pub(crate) struct DnDGrab {
data_source: Option<Resource<wl_data_source::WlDataSource>>, data_source: Option<Resource<wl_data_source::WlDataSource>>,
current_focus: Option<Resource<wl_surface::WlSurface>>, current_focus: Option<Resource<wl_surface::WlSurface>>,
pending_offers: Vec<Resource<wl_data_offer::WlDataOffer>>, pending_offers: Vec<Resource<wl_data_offer::WlDataOffer>>,
offer_data: Option<Arc<Mutex<OfferData>>>, offer_data: Option<Arc<Mutex<OfferData>>>,
origin: Resource<wl_surface::WlSurface>, origin: Resource<wl_surface::WlSurface>,
seat: Seat, seat: Seat,
action_choice: Arc<Mutex<F>>,
} }
impl<F: 'static> DnDGrab<F> { impl DnDGrab {
pub(crate) fn new( pub(crate) fn new(
source: Option<Resource<wl_data_source::WlDataSource>>, source: Option<Resource<wl_data_source::WlDataSource>>,
origin: Resource<wl_surface::WlSurface>, origin: Resource<wl_surface::WlSurface>,
seat: Seat, seat: Seat,
action_choice: Arc<Mutex<F>>, ) -> DnDGrab {
) -> DnDGrab<F> {
DnDGrab { DnDGrab {
data_source: source, data_source: source,
current_focus: None, current_focus: None,
@ -36,15 +34,11 @@ impl<F: 'static> DnDGrab<F> {
offer_data: None, offer_data: None,
origin, origin,
seat, seat,
action_choice,
} }
} }
} }
impl<F> PointerGrab for DnDGrab<F> impl PointerGrab for DnDGrab {
where
F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static,
{
fn motion( fn motion(
&mut self, &mut self,
_handle: &mut PointerInnerHandle, _handle: &mut PointerInnerHandle,
@ -99,6 +93,11 @@ where
.iter() .iter()
.filter(|d| d.same_client_as(&surface)) .filter(|d| d.same_client_as(&surface))
{ {
let action_choice = device
.user_data::<DataDeviceData>()
.unwrap()
.action_choice
.clone();
// create a data offer // create a data offer
let offer = client let offer = client
.create_resource::<wl_data_offer::WlDataOffer>(device.version()) .create_resource::<wl_data_offer::WlDataOffer>(device.version())
@ -107,7 +106,7 @@ where
offer, offer,
source.clone(), source.clone(),
offer_data.clone(), offer_data.clone(),
self.action_choice.clone(), action_choice,
) )
}).unwrap(); }).unwrap();
// advertize the offer to the client // advertize the offer to the client
@ -233,15 +232,12 @@ struct OfferData {
chosen_action: DndAction, chosen_action: DndAction,
} }
fn implement_dnd_data_offer<F>( fn implement_dnd_data_offer(
offer: NewResource<wl_data_offer::WlDataOffer>, offer: NewResource<wl_data_offer::WlDataOffer>,
source: Resource<wl_data_source::WlDataSource>, source: Resource<wl_data_source::WlDataSource>,
offer_data: Arc<Mutex<OfferData>>, offer_data: Arc<Mutex<OfferData>>,
action_choice: Arc<Mutex<F>>, action_choice: Arc<Mutex<FnMut(DndAction, DndAction) -> DndAction + Send + 'static>>,
) -> Resource<wl_data_offer::WlDataOffer> ) -> Resource<wl_data_offer::WlDataOffer> {
where
F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static,
{
use self::wl_data_offer::Request; use self::wl_data_offer::Request;
offer.implement( offer.implement(
move |req, offer| { move |req, offer| {

View File

@ -14,8 +14,10 @@ use wayland::seat::Seat;
mod data_source; mod data_source;
mod dnd_grab; mod dnd_grab;
mod server_dnd_grab;
pub use self::data_source::{with_source_metadata, SourceMetadata}; pub use self::data_source::{with_source_metadata, SourceMetadata};
pub use self::server_dnd_grab::ServerDndEvent;
/// Events that are generated by interactions of the clients with the data device /// Events that are generated by interactions of the clients with the data device
pub enum DataDeviceEvent { pub enum DataDeviceEvent {
@ -25,9 +27,9 @@ pub enum DataDeviceEvent {
DnDStarted(Option<Resource<wl_data_source::WlDataSource>>), DnDStarted(Option<Resource<wl_data_source::WlDataSource>>),
/// A client requested to read the server-set selection /// A client requested to read the server-set selection
SendSelection { SendSelection {
// the requested mime type /// the requested mime type
mime_type: String, mime_type: String,
// the fd to write into /// the fd to write into
fd: RawFd, fd: RawFd,
}, },
} }
@ -134,10 +136,7 @@ impl SeatData {
} }
let log = self.log.clone(); let log = self.log.clone();
let offer_meta = meta.clone(); let offer_meta = meta.clone();
let callback = dd let callback = dd.user_data::<DataDeviceData>().unwrap().callback.clone();
.user_data::<Arc<Mutex<FnMut(DataDeviceEvent) + Send + 'static>>>()
.unwrap()
.clone();
// create a corresponding data offer // create a corresponding data offer
let offer = client let offer = client
.create_resource::<wl_data_offer::WlDataOffer>(dd.version()) .create_resource::<wl_data_offer::WlDataOffer>(dd.version())
@ -252,6 +251,29 @@ pub fn set_data_device_selection(seat: &Seat, mime_types: Vec<String>) {
})); }));
} }
/// Start a drag'n'drop from a ressource controlled by the compositor
///
/// You'll receive events generated by the interaction of clients with your
/// drag'n'drop in the provided callback.
pub fn start_dnd<C>(seat: &Seat, serial: u32, metadata: SourceMetadata, callback: C)
where
C: FnMut(ServerDndEvent) + Send + 'static,
{
// TODO: same question as in set_data_device_focus
seat.user_data().insert_if_missing(|| {
Mutex::new(SeatData::new(
seat.arc.log.new(o!("smithay_module" => "data_device_mgr")),
))
});
if let Some(pointer) = seat.get_pointer() {
pointer.set_grab(
server_dnd_grab::ServerDnDGrab::new(metadata, seat.clone(), Arc::new(Mutex::new(callback))),
serial,
);
return;
}
}
fn implement_ddm<F, C>( fn implement_ddm<F, C>(
new_ddm: NewResource<wl_data_device_manager::WlDataDeviceManager>, new_ddm: NewResource<wl_data_device_manager::WlDataDeviceManager>,
callback: Arc<Mutex<C>>, callback: Arc<Mutex<C>>,
@ -293,6 +315,11 @@ where
) )
} }
struct DataDeviceData {
callback: Arc<Mutex<FnMut(DataDeviceEvent) + Send + 'static>>,
action_choice: Arc<Mutex<FnMut(DndAction, DndAction) -> DndAction + Send + 'static>>,
}
fn implement_data_device<F, C>( fn implement_data_device<F, C>(
new_dd: NewResource<wl_data_device::WlDataDevice>, new_dd: NewResource<wl_data_device::WlDataDevice>,
seat: Seat, seat: Seat,
@ -305,7 +332,10 @@ where
C: FnMut(DataDeviceEvent) + Send + 'static, C: FnMut(DataDeviceEvent) + Send + 'static,
{ {
use self::wl_data_device::Request; use self::wl_data_device::Request;
let callback2 = callback.clone(); let dd_data = DataDeviceData {
callback: callback.clone(),
action_choice,
};
new_dd.implement( new_dd.implement(
move |req, dd| match req { move |req, dd| match req {
Request::StartDrag { Request::StartDrag {
@ -319,10 +349,7 @@ where
if pointer.has_grab(serial) { if pointer.has_grab(serial) {
// The StartDrag is in response to a pointer implicit grab, all is good // The StartDrag is in response to a pointer implicit grab, all is good
(&mut *callback.lock().unwrap())(DataDeviceEvent::DnDStarted(source.clone())); (&mut *callback.lock().unwrap())(DataDeviceEvent::DnDStarted(source.clone()));
pointer.set_grab( pointer.set_grab(dnd_grab::DnDGrab::new(source, origin, seat.clone()), serial);
dnd_grab::DnDGrab::new(source, origin, seat.clone(), action_choice.clone()),
serial,
);
return; return;
} }
} }
@ -360,6 +387,6 @@ where
} }
}, },
None::<fn(_)>, None::<fn(_)>,
callback2 as Arc<Mutex<FnMut(DataDeviceEvent) + Send + 'static>>, dd_data,
) )
} }

View File

@ -0,0 +1,320 @@
use std::os::unix::io::RawFd;
use std::sync::{Arc, Mutex};
use wayland_server::{
protocol::{wl_data_device, wl_data_device_manager::DndAction, wl_data_offer, wl_pointer, wl_surface},
NewResource, Resource,
};
use wayland::seat::{AxisFrame, PointerGrab, PointerInnerHandle, Seat};
use super::{DataDeviceData, SeatData};
/// Event generated by the interactions of clients with a server innitiated drag'n'drop
pub enum ServerDndEvent {
/// The client chose an action
Action(DndAction),
/// The DnD resource was dropped by the user
///
/// After that, the client can still interract with your ressource
Dropped,
/// The Dnd was cancelled
///
/// The client can no longer interact
Cancelled,
/// The client requested for data to be sent
Send {
/// The requested mime type
mime_type: String,
/// The FD to write into
fd: RawFd,
},
/// The client has finished interacting with the resource
///
/// This can only happen after the resource was dropped.
Finished,
}
pub(crate) struct ServerDnDGrab<C: 'static> {
metadata: super::SourceMetadata,
current_focus: Option<Resource<wl_surface::WlSurface>>,
pending_offers: Vec<Resource<wl_data_offer::WlDataOffer>>,
offer_data: Option<Arc<Mutex<OfferData>>>,
seat: Seat,
callback: Arc<Mutex<C>>,
}
impl<C: 'static> ServerDnDGrab<C> {
pub(crate) fn new(
metadata: super::SourceMetadata,
seat: Seat,
callback: Arc<Mutex<C>>,
) -> ServerDnDGrab<C> {
ServerDnDGrab {
metadata,
current_focus: None,
pending_offers: Vec::with_capacity(1),
offer_data: None,
seat,
callback,
}
}
}
impl<C> PointerGrab for ServerDnDGrab<C>
where
C: FnMut(ServerDndEvent) + Send + 'static,
{
fn motion(
&mut self,
_handle: &mut PointerInnerHandle,
location: (f64, f64),
focus: Option<(Resource<wl_surface::WlSurface>, (f64, f64))>,
serial: u32,
time: u32,
) {
let (x, y) = location;
let seat_data = self
.seat
.user_data()
.get::<Mutex<SeatData>>()
.unwrap()
.lock()
.unwrap();
if focus.as_ref().map(|&(ref s, _)| s) != self.current_focus.as_ref() {
// focus changed, we need to make a leave if appropriate
if let Some(surface) = self.current_focus.take() {
for device in &seat_data.known_devices {
if device.same_client_as(&surface) {
device.send(wl_data_device::Event::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, (sx, sy))) = focus {
// early return if the surface is no longer valid
let client = match surface.client() {
Some(c) => c,
None => return,
};
if self.current_focus.is_none() {
// We entered a new surface, send the data offer
let offer_data = Arc::new(Mutex::new(OfferData {
active: true,
dropped: false,
accepted: true,
chosen_action: DndAction::empty(),
}));
for device in seat_data
.known_devices
.iter()
.filter(|d| d.same_client_as(&surface))
{
let action_choice = device
.user_data::<DataDeviceData>()
.unwrap()
.action_choice
.clone();
// create a data offer
let offer = client
.create_resource::<wl_data_offer::WlDataOffer>(device.version())
.map(|offer| {
implement_dnd_data_offer(
offer,
self.metadata.clone(),
offer_data.clone(),
self.callback.clone(),
action_choice,
)
}).unwrap();
// advertize the offer to the client
device.send(wl_data_device::Event::DataOffer { id: offer.clone() });
for mime_type in self.metadata.mime_types.iter().cloned() {
offer.send(wl_data_offer::Event::Offer { mime_type })
}
offer.send(wl_data_offer::Event::SourceActions {
source_actions: self.metadata.dnd_action.to_raw(),
});
device.send(wl_data_device::Event::Enter {
serial,
x: x - sx,
y: y - sy,
surface: surface.clone(),
id: Some(offer.clone()),
});
self.pending_offers.push(offer);
}
self.offer_data = Some(offer_data);
self.current_focus = Some(surface);
} else {
// make a move
for device in &seat_data.known_devices {
if device.same_client_as(&surface) {
device.send(wl_data_device::Event::Motion {
time,
x: x - sx,
y: y - sy,
});
}
}
}
}
}
fn button(
&mut self,
handle: &mut PointerInnerHandle,
_button: u32,
_state: wl_pointer::ButtonState,
serial: u32,
time: u32,
) {
if handle.current_pressed().len() == 0 {
// the user dropped, proceed to the drop
let seat_data = self
.seat
.user_data()
.get::<Mutex<SeatData>>()
.unwrap()
.lock()
.unwrap();
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 {
for device in &seat_data.known_devices {
if device.same_client_as(surface) {
if validated {
device.send(wl_data_device::Event::Drop);
} else {
device.send(wl_data_device::Event::Leave);
}
}
}
}
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;
}
}
let mut callback = self.callback.lock().unwrap();
(&mut *callback)(ServerDndEvent::Dropped);
if !validated {
(&mut *callback)(ServerDndEvent::Cancelled);
}
// in all cases abandon the drop
// no more buttons are pressed, release the grab
handle.unset_grab(serial, time);
}
}
fn axis(&mut self, handle: &mut PointerInnerHandle, details: AxisFrame) {
// we just forward the axis events as is
handle.axis(details);
}
}
struct OfferData {
active: bool,
dropped: bool,
accepted: bool,
chosen_action: DndAction,
}
fn implement_dnd_data_offer<C>(
offer: NewResource<wl_data_offer::WlDataOffer>,
metadata: super::SourceMetadata,
offer_data: Arc<Mutex<OfferData>>,
callback: Arc<Mutex<C>>,
action_choice: Arc<Mutex<FnMut(DndAction, DndAction) -> DndAction + Send + 'static>>,
) -> Resource<wl_data_offer::WlDataOffer>
where
C: FnMut(ServerDndEvent) + Send + 'static,
{
use self::wl_data_offer::Request;
offer.implement(
move |req, offer| {
let mut data = offer_data.lock().unwrap();
match req {
Request::Accept { serial: _, mime_type } => {
if let Some(mtype) = mime_type {
data.accepted = metadata.mime_types.contains(&mtype);
} else {
data.accepted = false;
}
}
Request::Receive { mime_type, fd } => {
// check if the source and associated mime type is still valid
if metadata.mime_types.contains(&mime_type) && data.active {
(&mut *callback.lock().unwrap())(ServerDndEvent::Send { mime_type, fd });
}
}
Request::Destroy => {}
Request::Finish => {
if !data.active {
offer.post_error(
wl_data_offer::Error::InvalidFinish as u32,
"Cannot finish a data offer that is no longer active.".into(),
);
}
if !data.accepted {
offer.post_error(
wl_data_offer::Error::InvalidFinish as u32,
"Cannot finish a data offer that has not been accepted.".into(),
);
}
if !data.dropped {
offer.post_error(
wl_data_offer::Error::InvalidFinish as u32,
"Cannot finish a data offer that has not been dropped.".into(),
);
}
if data.chosen_action.is_empty() {
offer.post_error(
wl_data_offer::Error::InvalidFinish as u32,
"Cannot finish a data offer with no valid action.".into(),
);
}
(&mut *callback.lock().unwrap())(ServerDndEvent::Finished);
data.active = false;
}
Request::SetActions {
dnd_actions,
preferred_action,
} => {
let preferred_action = DndAction::from_bits_truncate(preferred_action);
if ![DndAction::Move, DndAction::Copy, DndAction::Ask].contains(&preferred_action) {
offer.post_error(
wl_data_offer::Error::InvalidAction as u32,
"Invalid preferred action.".into(),
);
}
let possible_actions = metadata.dnd_action & DndAction::from_bits_truncate(dnd_actions);
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(),
});
(&mut *callback.lock().unwrap())(ServerDndEvent::Action(data.chosen_action));
}
}
},
None::<fn(_)>,
(),
)
}