data_device: basic DnD support
This commit is contained in:
parent
3e28358668
commit
81956c8fd0
|
@ -68,9 +68,9 @@ pub fn init_shell(
|
|||
compositor_token,
|
||||
move |shell_event| match shell_event {
|
||||
XdgRequest::NewToplevel { surface } => {
|
||||
// place the window at a random location in the [0;300]x[0;300] square
|
||||
// place the window at a random location in the [0;800]x[0;800] square
|
||||
use rand::distributions::{IndependentSample, Range};
|
||||
let range = Range::new(0, 300);
|
||||
let range = Range::new(0, 800);
|
||||
let mut rng = rand::thread_rng();
|
||||
let x = range.ind_sample(&mut rng);
|
||||
let y = range.ind_sample(&mut rng);
|
||||
|
|
|
@ -0,0 +1,325 @@
|
|||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use wayland_server::{
|
||||
protocol::{
|
||||
wl_data_device, wl_data_device_manager::DndAction, wl_data_offer, wl_data_source, wl_pointer,
|
||||
wl_surface,
|
||||
},
|
||||
NewResource, Resource,
|
||||
};
|
||||
|
||||
use wayland::seat::{AxisFrame, PointerGrab, PointerInnerHandle, Seat};
|
||||
|
||||
use super::{with_source_metadata, SeatData};
|
||||
|
||||
pub(crate) struct DnDGrab {
|
||||
data_source: Option<Resource<wl_data_source::WlDataSource>>,
|
||||
current_focus: Option<Resource<wl_surface::WlSurface>>,
|
||||
pending_offers: Vec<Resource<wl_data_offer::WlDataOffer>>,
|
||||
offer_data: Option<Arc<Mutex<OfferData>>>,
|
||||
origin: Resource<wl_surface::WlSurface>,
|
||||
seat: Seat,
|
||||
}
|
||||
|
||||
impl DnDGrab {
|
||||
pub(crate) fn new(
|
||||
source: Option<Resource<wl_data_source::WlDataSource>>,
|
||||
origin: Resource<wl_surface::WlSurface>,
|
||||
seat: Seat,
|
||||
) -> DnDGrab {
|
||||
DnDGrab {
|
||||
data_source: source,
|
||||
current_focus: None,
|
||||
pending_offers: Vec::with_capacity(1),
|
||||
offer_data: None,
|
||||
origin,
|
||||
seat,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PointerGrab for DnDGrab {
|
||||
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() {
|
||||
// only leave if there is a data source or we are on the original client
|
||||
if self.data_source.is_some() || self.origin.same_client_as(&surface) {
|
||||
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 if appropriate
|
||||
if let Some(ref source) = self.data_source {
|
||||
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))
|
||||
{
|
||||
// create a data offer
|
||||
let offer = client
|
||||
.create_resource::<wl_data_offer::WlDataOffer>(device.version())
|
||||
.map(|offer| implement_dnd_data_offer(offer, source.clone(), offer_data.clone()))
|
||||
.unwrap();
|
||||
// advertize the offer to the client
|
||||
device.send(wl_data_device::Event::DataOffer { id: offer.clone() });
|
||||
with_source_metadata(source, |meta| {
|
||||
for mime_type in meta.mime_types.iter().cloned() {
|
||||
offer.send(wl_data_offer::Event::Offer { mime_type })
|
||||
}
|
||||
offer.send(wl_data_offer::Event::SourceActions {
|
||||
source_actions: meta.dnd_action.to_raw(),
|
||||
});
|
||||
}).unwrap();
|
||||
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);
|
||||
} else {
|
||||
// only send if we are on a surface of the same client
|
||||
if self.origin.same_client_as(&surface) {
|
||||
for device in &seat_data.known_devices {
|
||||
if device.same_client_as(&surface) {
|
||||
device.send(wl_data_device::Event::Enter {
|
||||
serial,
|
||||
x: x - sx,
|
||||
y: y - sy,
|
||||
surface: surface.clone(),
|
||||
id: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.current_focus = Some(surface);
|
||||
} else {
|
||||
// make a move
|
||||
if self.data_source.is_some() || self.origin.same_client_as(&surface) {
|
||||
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 {
|
||||
if self.data_source.is_some() || self.origin.same_client_as(&surface) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
if let Some(ref source) = self.data_source {
|
||||
source.send(wl_data_source::Event::DndDropPerformed);
|
||||
if !validated {
|
||||
source.send(wl_data_source::Event::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(
|
||||
offer: NewResource<wl_data_offer::WlDataOffer>,
|
||||
source: Resource<wl_data_source::WlDataSource>,
|
||||
offer_data: Arc<Mutex<OfferData>>,
|
||||
) -> Resource<wl_data_offer::WlDataOffer> {
|
||||
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 {
|
||||
if let Err(()) = 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.is_alive()
|
||||
&& data.active;
|
||||
if valid {
|
||||
source.send(wl_data_source::Event::Send { mime_type, fd });
|
||||
}
|
||||
let _ = ::nix::unistd::close(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(),
|
||||
);
|
||||
}
|
||||
source.send(wl_data_source::Event::DndFinished);
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
offer.send(wl_data_offer::Event::Action {
|
||||
dnd_action: data.chosen_action.to_raw(),
|
||||
});
|
||||
source.send(wl_data_source::Event::Action {
|
||||
dnd_action: data.chosen_action.to_raw(),
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
None::<fn(_)>,
|
||||
(),
|
||||
)
|
||||
}
|
|
@ -1,15 +1,14 @@
|
|||
use std::sync::Mutex;
|
||||
|
||||
use wayland_server::{
|
||||
protocol::{
|
||||
wl_data_device, wl_data_device_manager, wl_data_offer, wl_data_source, wl_pointer, wl_surface,
|
||||
},
|
||||
protocol::{wl_data_device, wl_data_device_manager, wl_data_offer, wl_data_source},
|
||||
Client, Display, Global, NewResource, Resource,
|
||||
};
|
||||
|
||||
use wayland::seat::{AxisFrame, PointerGrab, PointerInnerHandle, Seat};
|
||||
use wayland::seat::Seat;
|
||||
|
||||
mod data_source;
|
||||
mod dnd_grab;
|
||||
|
||||
pub use self::data_source::{with_source_metadata, SourceMetadata};
|
||||
|
||||
|
@ -201,14 +200,7 @@ 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(
|
||||
DnDGrab {
|
||||
data_source: source,
|
||||
origin,
|
||||
seat: seat.clone(),
|
||||
},
|
||||
serial,
|
||||
);
|
||||
pointer.set_grab(dnd_grab::DnDGrab::new(source, origin, seat.clone()), serial);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -248,35 +240,3 @@ fn implement_data_device(
|
|||
(),
|
||||
)
|
||||
}
|
||||
|
||||
struct DnDGrab {
|
||||
data_source: Option<Resource<wl_data_source::WlDataSource>>,
|
||||
origin: Resource<wl_surface::WlSurface>,
|
||||
seat: Seat,
|
||||
}
|
||||
|
||||
impl PointerGrab for DnDGrab {
|
||||
fn motion(
|
||||
&mut self,
|
||||
handle: &mut PointerInnerHandle,
|
||||
location: (f64, f64),
|
||||
focus: Option<(Resource<wl_surface::WlSurface>, (f64, f64))>,
|
||||
serial: u32,
|
||||
time: u32,
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
fn button(
|
||||
&mut self,
|
||||
handle: &mut PointerInnerHandle,
|
||||
button: u32,
|
||||
state: wl_pointer::ButtonState,
|
||||
serial: u32,
|
||||
time: u32,
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
fn axis(&mut self, handle: &mut PointerInnerHandle, details: AxisFrame) {}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ enum GrabStatus {
|
|||
struct PointerInternal {
|
||||
known_pointers: Vec<Resource<WlPointer>>,
|
||||
focus: Option<(Resource<WlSurface>, (f64, f64))>,
|
||||
pending_focus: Option<(Resource<WlSurface>, (f64, f64))>,
|
||||
location: (f64, f64),
|
||||
grab: GrabStatus,
|
||||
pressed_buttons: Vec<u32>,
|
||||
|
@ -28,6 +29,7 @@ impl PointerInternal {
|
|||
PointerInternal {
|
||||
known_pointers: Vec::new(),
|
||||
focus: None,
|
||||
pending_focus: None,
|
||||
location: (0.0, 0.0),
|
||||
grab: GrabStatus::None,
|
||||
pressed_buttons: Vec::new(),
|
||||
|
@ -138,7 +140,9 @@ impl PointerHandle {
|
|||
serial: u32,
|
||||
time: u32,
|
||||
) {
|
||||
self.inner.lock().unwrap().with_grab(move |mut handle, grab| {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.pending_focus = focus.clone();
|
||||
inner.with_grab(move |mut handle, grab| {
|
||||
grab.motion(&mut handle, location, focus, serial, time);
|
||||
});
|
||||
}
|
||||
|
@ -226,8 +230,14 @@ impl<'a> PointerInnerHandle<'a> {
|
|||
}
|
||||
|
||||
/// Remove any current grab on this pointer, resetting it to the default behavior
|
||||
pub fn unset_grab(&mut self) {
|
||||
///
|
||||
/// This will also restore the focus of the underlying pointer
|
||||
pub fn unset_grab(&mut self, serial: u32, time: u32) {
|
||||
self.inner.grab = GrabStatus::None;
|
||||
// restore the focus
|
||||
let location = self.current_location();
|
||||
let focus = self.inner.pending_focus.take();
|
||||
self.motion(location, focus, serial, time);
|
||||
}
|
||||
|
||||
/// Access the current focus of this pointer
|
||||
|
@ -605,10 +615,7 @@ impl PointerGrab for ClickGrab {
|
|||
handle.button(button, state, serial, time);
|
||||
if handle.current_pressed().len() == 0 {
|
||||
// no more buttons are pressed, release the grab
|
||||
handle.unset_grab();
|
||||
// restore the focus
|
||||
let location = handle.current_location();
|
||||
handle.motion(location, self.pending_focus.clone(), serial, time);
|
||||
handle.unset_grab(serial, time);
|
||||
}
|
||||
}
|
||||
fn axis(&mut self, handle: &mut PointerInnerHandle, details: AxisFrame) {
|
||||
|
|
Loading…
Reference in New Issue