diff --git a/anvil/src/shell.rs b/anvil/src/shell.rs index dd68aa1..07bc0d9 100644 --- a/anvil/src/shell.rs +++ b/anvil/src/shell.rs @@ -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); diff --git a/src/wayland/data_device/dnd_grab.rs b/src/wayland/data_device/dnd_grab.rs new file mode 100644 index 0000000..3a6bb0f --- /dev/null +++ b/src/wayland/data_device/dnd_grab.rs @@ -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>, + current_focus: Option>, + pending_offers: Vec>, + offer_data: Option>>, + origin: Resource, + seat: Seat, +} + +impl DnDGrab { + pub(crate) fn new( + source: Option>, + origin: Resource, + 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, (f64, f64))>, + serial: u32, + time: u32, + ) { + let (x, y) = location; + let seat_data = self + .seat + .user_data() + .get::>() + .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::(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::>() + .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, + source: Resource, + offer_data: Arc>, +) -> Resource { + 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::, + (), + ) +} diff --git a/src/wayland/data_device/mod.rs b/src/wayland/data_device/mod.rs index 9cab6dd..afa03ed 100644 --- a/src/wayland/data_device/mod.rs +++ b/src/wayland/data_device/mod.rs @@ -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>, - origin: Resource, - seat: Seat, -} - -impl PointerGrab for DnDGrab { - fn motion( - &mut self, - handle: &mut PointerInnerHandle, - location: (f64, f64), - focus: Option<(Resource, (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) {} -} diff --git a/src/wayland/seat/pointer.rs b/src/wayland/seat/pointer.rs index c8a6951..39978d3 100644 --- a/src/wayland/seat/pointer.rs +++ b/src/wayland/seat/pointer.rs @@ -18,6 +18,7 @@ enum GrabStatus { struct PointerInternal { known_pointers: Vec>, focus: Option<(Resource, (f64, f64))>, + pending_focus: Option<(Resource, (f64, f64))>, location: (f64, f64), grab: GrabStatus, pressed_buttons: Vec, @@ -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) {