From 03cf6ed35f26299dde1fcd850691e650ef79ac71 Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Sat, 17 Nov 2018 19:08:47 +0100 Subject: [PATCH 01/12] seat: KeyboardHandle::has_focus --- src/wayland/seat/keyboard.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/wayland/seat/keyboard.rs b/src/wayland/seat/keyboard.rs index 0ca99c6..2aaa0e2 100644 --- a/src/wayland/seat/keyboard.rs +++ b/src/wayland/seat/keyboard.rs @@ -11,7 +11,7 @@ use wayland_server::{ wl_keyboard::{Event, KeyState as WlKeyState, KeymapFormat, Request, WlKeyboard}, wl_surface::WlSurface, }, - NewResource, Resource, + Client, NewResource, Resource, }; use xkbcommon::xkb; pub use xkbcommon::xkb::{keysyms, Keysym}; @@ -392,6 +392,19 @@ impl KeyboardHandle { } } + /// Check if given client currently has keyboard focus + pub fn has_focus(&self, client: &Client) -> bool { + self.arc + .internal + .lock() + .unwrap() + .focus + .as_ref() + .and_then(|f| f.client()) + .map(|c| c.equals(client)) + .unwrap_or(false) + } + /// Register a new keyboard to this handler /// /// The keymap will automatically be sent to it From afd92d0a3d9a7f591196cea1abc3c4487d6ee6cd Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Sat, 17 Nov 2018 23:10:48 +0100 Subject: [PATCH 02/12] seat: add utilities to Seat --- src/wayland/seat/mod.rs | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/wayland/seat/mod.rs b/src/wayland/seat/mod.rs index 65e0815..41e60d1 100644 --- a/src/wayland/seat/mod.rs +++ b/src/wayland/seat/mod.rs @@ -144,6 +144,13 @@ impl Seat { (seat, global) } + /// Attempt to retrieve a `Seat` from an existing resource + pub fn from_resource(seat: &Resource) -> Option { + seat.user_data::>>() + .cloned() + .map(|inner| Seat { inner }) + } + /// Adds the pointer capability to this seat /// /// You are provided a `PointerHandle`, which allows you to send input events @@ -166,6 +173,11 @@ impl Seat { pointer } + /// Access the pointer of this seat if any + pub fn get_pointer(&self) -> Option { + self.inner.lock().unwrap().pointer.clone() + } + /// Remove the pointer capability from this seat /// /// Clients will be appropriately notified. @@ -228,6 +240,11 @@ impl Seat { Ok(keyboard) } + /// Access the keyboard of this seat if any + pub fn get_keyboard(&self) -> Option { + self.inner.lock().unwrap().keyboard.clone() + } + /// Remove the keyboard capability from this seat /// /// Clients will be appropriately notified. @@ -252,8 +269,8 @@ fn implement_seat( ) -> Resource { let dest_inner = inner.clone(); new_seat.implement( - move |request, _seat| { - let inner = inner.lock().unwrap(); + move |request, seat| { + let inner = seat.user_data::>>().unwrap().lock().unwrap(); match request { wl_seat::Request::GetPointer { id } => { let pointer = self::pointer::implement_pointer(id, inner.pointer.as_ref()); @@ -287,6 +304,6 @@ fn implement_seat( .known_seats .retain(|s| !s.equals(&seat)); }), - (), + inner, ) } From 5e9ad96b0fc72f43e6db3974765e0dbaaf2cf0ec Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Sun, 18 Nov 2018 10:58:04 +0100 Subject: [PATCH 03/12] seat: add a keyboard focus hook --- anvil/src/udev.rs | 2 +- anvil/src/winit.rs | 2 +- src/wayland/seat/keyboard.rs | 34 +++++++++++++++++++++++++++------- src/wayland/seat/mod.rs | 31 ++++++++++++++++++++++++++----- 4 files changed, 55 insertions(+), 14 deletions(-) diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index 97695ce..3232fcf 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -111,7 +111,7 @@ pub fn run_udev(mut display: Display, mut event_loop: EventLoop<()>, log: Logger let pointer = w_seat.add_pointer(); let keyboard = w_seat - .add_keyboard(XkbConfig::default(), 1000, 500) + .add_keyboard(XkbConfig::default(), 1000, 500, |_, _| {}) .expect("Failed to initialize the keyboard"); let (output, _output_global) = Output::new( diff --git a/anvil/src/winit.rs b/anvil/src/winit.rs index 29566bb..9d4d479 100644 --- a/anvil/src/winit.rs +++ b/anvil/src/winit.rs @@ -57,7 +57,7 @@ pub fn run_winit(display: &mut Display, event_loop: &mut EventLoop<()>, log: Log let pointer = seat.add_pointer(); let keyboard = seat - .add_keyboard(XkbConfig::default(), 1000, 500) + .add_keyboard(XkbConfig::default(), 1000, 500, |_, _| {}) .expect("Failed to initialize the keyboard"); let (output, _) = Output::new( diff --git a/src/wayland/seat/keyboard.rs b/src/wayland/seat/keyboard.rs index 2aaa0e2..55c2622 100644 --- a/src/wayland/seat/keyboard.rs +++ b/src/wayland/seat/keyboard.rs @@ -112,6 +112,7 @@ struct KbdInternal { state: xkb::State, repeat_rate: i32, repeat_delay: i32, + focus_hook: Box>)>, } // This is OK because all parts of `xkb` will remain on the @@ -119,7 +120,12 @@ struct KbdInternal { unsafe impl Send for KbdInternal {} impl KbdInternal { - fn new(xkb_config: XkbConfig, repeat_rate: i32, repeat_delay: i32) -> Result { + fn new( + xkb_config: XkbConfig, + repeat_rate: i32, + repeat_delay: i32, + focus_hook: Box>)>, + ) -> Result { // we create a new contex for each keyboard because libxkbcommon is actually NOT threadsafe // so confining it inside the KbdInternal allows us to use Rusts mutability rules to make // sure nothing goes wrong. @@ -146,6 +152,7 @@ impl KbdInternal { state, repeat_rate, repeat_delay, + focus_hook, }) } @@ -219,21 +226,26 @@ pub enum Error { } /// Create a keyboard handler from a set of RMLVO rules -pub(crate) fn create_keyboard_handler( +pub(crate) fn create_keyboard_handler( xkb_config: XkbConfig, repeat_delay: i32, repeat_rate: i32, logger: &::slog::Logger, -) -> Result { + focus_hook: F, +) -> Result +where + F: FnMut(Option<&Resource>) + 'static, +{ let log = logger.new(o!("smithay_module" => "xkbcommon_handler")); info!(log, "Initializing a xkbcommon handler with keymap query"; "rules" => xkb_config.rules, "model" => xkb_config.model, "layout" => xkb_config.layout, "variant" => xkb_config.variant, "options" => &xkb_config.options ); - let internal = KbdInternal::new(xkb_config, repeat_rate, repeat_delay).map_err(|_| { - debug!(log, "Loading keymap failed"); - Error::BadKeymap - })?; + let internal = + KbdInternal::new(xkb_config, repeat_rate, repeat_delay, Box::new(focus_hook)).map_err(|_| { + debug!(log, "Loading keymap failed"); + Error::BadKeymap + })?; info!(log, "Loaded Keymap"; "name" => internal.keymap.layouts().next()); @@ -382,6 +394,14 @@ impl KeyboardHandle { keys: keys.clone(), }); }); + { + let KbdInternal { + ref focus, + ref mut focus_hook, + .. + } = *guard; + focus_hook(focus.as_ref()); + } if guard.focus.is_some() { trace!(self.arc.logger, "Focus set to new surface"); } else { diff --git a/src/wayland/seat/mod.rs b/src/wayland/seat/mod.rs index 41e60d1..5853475 100644 --- a/src/wayland/seat/mod.rs +++ b/src/wayland/seat/mod.rs @@ -64,7 +64,10 @@ pub use self::{ pointer::{AxisFrame, PointerGrab, PointerHandle, PointerInnerHandle}, }; -use wayland_server::{protocol::wl_seat, Display, Global, NewResource, Resource}; +use wayland_server::{ + protocol::{wl_seat, wl_surface}, + Display, Global, NewResource, Resource, +}; struct Inner { log: ::slog::Logger, @@ -101,7 +104,11 @@ impl Inner { /// /// It is directly inserted in the event loop by its `new` method. /// +/// This is an handle to the inner logic, it can be cloned and shared accross +/// threads. +/// /// See module-level documentation for details of use. +#[derive(Clone)] pub struct Seat { inner: Arc>, } @@ -217,18 +224,32 @@ impl Seat { /// }, /// 1000, /// 500, + /// |seat, focus| { + /// /* This closure is called whenever the keyboard focus + /// * changes, with the new focus as argument */ + /// } /// ) /// .expect("Failed to initialize the keyboard"); /// ``` - pub fn add_keyboard( + pub fn add_keyboard( &mut self, xkb_config: keyboard::XkbConfig, repeat_delay: i32, repeat_rate: i32, - ) -> Result { + mut focus_hook: F, + ) -> Result + where + F: FnMut(&Seat, Option<&Resource>) + 'static, + { + let me = self.clone(); let mut inner = self.inner.lock().unwrap(); - let keyboard = - self::keyboard::create_keyboard_handler(xkb_config, repeat_delay, repeat_rate, &inner.log)?; + let keyboard = self::keyboard::create_keyboard_handler( + xkb_config, + repeat_delay, + repeat_rate, + &inner.log, + move |focus| focus_hook(&me, focus), + )?; if inner.keyboard.is_some() { // there is already a keyboard, remove it and notify the clients // of the change From f1251a31e665202d2cb0ceb1bdd04f9f24af907e Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Sun, 18 Nov 2018 18:08:37 +0100 Subject: [PATCH 04/12] seat: user data mechanism for Seat --- Cargo.toml | 1 + src/lib.rs | 1 + src/wayland/seat/mod.rs | 80 +++++++++++++++++++++++++---------------- 3 files changed, 51 insertions(+), 31 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6407095..64de8fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ members = [ "anvil" ] [dependencies] wayland-server = "0.21.1" wayland-sys = "0.21.1" +wayland-commons = "0.21.1" nix = "0.11" xkbcommon = "0.2.1" tempfile = "2.1.5" diff --git a/src/lib.rs b/src/lib.rs index 1e00641..24e5b49 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ pub extern crate image; #[cfg_attr(feature = "backend_session", macro_use)] extern crate nix; extern crate tempfile; +pub extern crate wayland_commons; pub extern crate wayland_protocols; pub extern crate wayland_server; extern crate wayland_sys; diff --git a/src/wayland/seat/mod.rs b/src/wayland/seat/mod.rs index 5853475..242e472 100644 --- a/src/wayland/seat/mod.rs +++ b/src/wayland/seat/mod.rs @@ -64,19 +64,26 @@ pub use self::{ pointer::{AxisFrame, PointerGrab, PointerHandle, PointerInnerHandle}, }; +use wayland_commons::utils::UserDataMap; + use wayland_server::{ protocol::{wl_seat, wl_surface}, Display, Global, NewResource, Resource, }; struct Inner { - log: ::slog::Logger, - name: String, pointer: Option, keyboard: Option, known_seats: Vec>, } +struct SeatArc { + inner: Mutex, + user_data: UserDataMap, + log: ::slog::Logger, + name: String, +} + impl Inner { fn compute_caps(&self) -> wl_seat::Capability { let mut caps = wl_seat::Capability::empty(); @@ -110,7 +117,7 @@ impl Inner { /// See module-level documentation for details of use. #[derive(Clone)] pub struct Seat { - inner: Arc>, + arc: Arc, } impl Seat { @@ -127,20 +134,23 @@ impl Seat { L: Into>, { let log = ::slog_or_stdlog(logger); - let inner = Arc::new(Mutex::new(Inner { + let arc = Arc::new(SeatArc { + inner: Mutex::new(Inner { + pointer: None, + keyboard: None, + known_seats: Vec::new(), + }), log: log.new(o!("smithay_module" => "seat_handler", "seat_name" => name.clone())), name, - pointer: None, - keyboard: None, - known_seats: Vec::new(), - })); - let seat = Seat { inner: inner.clone() }; + user_data: UserDataMap::new(), + }); + let seat = Seat { arc: arc.clone() }; let global = display.create_global(5, move |new_seat, _version| { - let seat = implement_seat(new_seat, inner.clone()); - let mut inner = inner.lock().unwrap(); + let seat = implement_seat(new_seat, arc.clone()); + let mut inner = arc.inner.lock().unwrap(); if seat.version() >= 2 { seat.send(wl_seat::Event::Name { - name: inner.name.clone(), + name: arc.name.clone(), }); } seat.send(wl_seat::Event::Capabilities { @@ -153,9 +163,12 @@ impl Seat { /// Attempt to retrieve a `Seat` from an existing resource pub fn from_resource(seat: &Resource) -> Option { - seat.user_data::>>() - .cloned() - .map(|inner| Seat { inner }) + seat.user_data::>().cloned().map(|arc| Seat { arc }) + } + + /// Acces the `UserDataMap` associated with this `Seat` + pub fn user_data(&self) -> &UserDataMap { + &self.arc.user_data } /// Adds the pointer capability to this seat @@ -167,7 +180,7 @@ impl Seat { /// will overwrite it, and will be seen by the clients as if the /// mouse was unplugged and a new one was plugged. pub fn add_pointer(&mut self) -> PointerHandle { - let mut inner = self.inner.lock().unwrap(); + let mut inner = self.arc.inner.lock().unwrap(); let pointer = self::pointer::create_pointer_handler(); if inner.pointer.is_some() { // there is already a pointer, remove it and notify the clients @@ -182,14 +195,14 @@ impl Seat { /// Access the pointer of this seat if any pub fn get_pointer(&self) -> Option { - self.inner.lock().unwrap().pointer.clone() + self.arc.inner.lock().unwrap().pointer.clone() } /// Remove the pointer capability from this seat /// /// Clients will be appropriately notified. pub fn remove_pointer(&mut self) { - let mut inner = self.inner.lock().unwrap(); + let mut inner = self.arc.inner.lock().unwrap(); if inner.pointer.is_some() { inner.pointer = None; inner.send_all_caps(); @@ -242,12 +255,12 @@ impl Seat { F: FnMut(&Seat, Option<&Resource>) + 'static, { let me = self.clone(); - let mut inner = self.inner.lock().unwrap(); + let mut inner = self.arc.inner.lock().unwrap(); let keyboard = self::keyboard::create_keyboard_handler( xkb_config, repeat_delay, repeat_rate, - &inner.log, + &self.arc.log, move |focus| focus_hook(&me, focus), )?; if inner.keyboard.is_some() { @@ -263,14 +276,14 @@ impl Seat { /// Access the keyboard of this seat if any pub fn get_keyboard(&self) -> Option { - self.inner.lock().unwrap().keyboard.clone() + self.arc.inner.lock().unwrap().keyboard.clone() } /// Remove the keyboard capability from this seat /// /// Clients will be appropriately notified. pub fn remove_keyboard(&mut self) { - let mut inner = self.inner.lock().unwrap(); + let mut inner = self.arc.inner.lock().unwrap(); if inner.keyboard.is_some() { inner.keyboard = None; inner.send_all_caps(); @@ -279,19 +292,23 @@ impl Seat { /// Checks whether a given `WlSeat` is associated with this `Seat` pub fn owns(&self, seat: &Resource) -> bool { - let inner = self.inner.lock().unwrap(); + let inner = self.arc.inner.lock().unwrap(); inner.known_seats.iter().any(|s| s.equals(seat)) } } -fn implement_seat( - new_seat: NewResource, - inner: Arc>, -) -> Resource { - let dest_inner = inner.clone(); +impl ::std::cmp::PartialEq for Seat { + fn eq(&self, other: &Seat) -> bool { + Arc::ptr_eq(&self.arc, &other.arc) + } +} + +fn implement_seat(new_seat: NewResource, arc: Arc) -> Resource { + let dest_arc = arc.clone(); new_seat.implement( move |request, seat| { - let inner = seat.user_data::>>().unwrap().lock().unwrap(); + let arc = seat.user_data::>().unwrap(); + let inner = arc.inner.lock().unwrap(); match request { wl_seat::Request::GetPointer { id } => { let pointer = self::pointer::implement_pointer(id, inner.pointer.as_ref()); @@ -319,12 +336,13 @@ fn implement_seat( } }, Some(move |seat| { - dest_inner + dest_arc + .inner .lock() .unwrap() .known_seats .retain(|s| !s.equals(&seat)); }), - inner, + arc, ) } From 0434cbb90d9971aea8af9da308221e71e03a824d Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Sun, 18 Nov 2018 19:39:33 +0100 Subject: [PATCH 05/12] data_device: basic selection support --- anvil/src/udev.rs | 8 +- anvil/src/winit.rs | 9 +- src/wayland/data_device/data_source.rs | 50 +++++ src/wayland/data_device/mod.rs | 282 +++++++++++++++++++++++++ src/wayland/mod.rs | 1 + src/wayland/seat/mod.rs | 6 +- 6 files changed, 349 insertions(+), 7 deletions(-) create mode 100644 src/wayland/data_device/data_source.rs create mode 100644 src/wayland/data_device/mod.rs diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index 3232fcf..8328c40 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -42,6 +42,7 @@ use smithay::{ input::Libinput, wayland::{ compositor::CompositorToken, + data_device::{init_data_device, set_data_device_focus}, output::{Mode, Output, PhysicalProperties}, seat::{Seat, XkbConfig}, shm::init_shm_global, @@ -107,12 +108,15 @@ pub fn run_udev(mut display: Display, mut event_loop: EventLoop<()>, log: Logger let udev_session_id = notifier.register(&mut udev_backend); + init_data_device(&mut display.borrow_mut(), log.clone()); + let (mut w_seat, _) = Seat::new(&mut display.borrow_mut(), session.seat(), log.clone()); let pointer = w_seat.add_pointer(); let keyboard = w_seat - .add_keyboard(XkbConfig::default(), 1000, 500, |_, _| {}) - .expect("Failed to initialize the keyboard"); + .add_keyboard(XkbConfig::default(), 1000, 500, |seat, focus| { + set_data_device_focus(seat, focus.and_then(|s| s.client())) + }).expect("Failed to initialize the keyboard"); let (output, _output_global) = Output::new( &mut display.borrow_mut(), diff --git a/anvil/src/winit.rs b/anvil/src/winit.rs index 9d4d479..8890e88 100644 --- a/anvil/src/winit.rs +++ b/anvil/src/winit.rs @@ -11,6 +11,7 @@ use smithay::{ winit, }, wayland::{ + data_device::{init_data_device, set_data_device_focus}, output::{Mode, Output, PhysicalProperties}, seat::{Seat, XkbConfig}, shm::init_shm_global, @@ -53,12 +54,16 @@ pub fn run_winit(display: &mut Display, event_loop: &mut EventLoop<()>, log: Log let (compositor_token, _, _, window_map) = init_shell(display, log.clone()); + init_data_device(display, log.clone()); + let (mut seat, _) = Seat::new(display, "winit".into(), log.clone()); let pointer = seat.add_pointer(); + let keyboard = seat - .add_keyboard(XkbConfig::default(), 1000, 500, |_, _| {}) - .expect("Failed to initialize the keyboard"); + .add_keyboard(XkbConfig::default(), 1000, 500, |seat, focus| { + set_data_device_focus(seat, focus.and_then(|s| s.client())) + }).expect("Failed to initialize the keyboard"); let (output, _) = Output::new( display, diff --git a/src/wayland/data_device/data_source.rs b/src/wayland/data_device/data_source.rs new file mode 100644 index 0000000..b1d4522 --- /dev/null +++ b/src/wayland/data_device/data_source.rs @@ -0,0 +1,50 @@ +use std::sync::Mutex; + +use wayland_server::{ + protocol::{ + wl_data_device_manager::DndAction, + wl_data_source::{Request, WlDataSource}, + }, + NewResource, Resource, +}; + +/// The metadata describing a data source +#[derive(Debug, Clone)] +pub struct SourceMetadata { + /// The MIME types supported by this source + pub mime_types: Vec, + /// The Drag'n'Drop actions supported by this source + pub dnd_action: DndAction, +} + +pub(crate) fn implement_data_source(src: NewResource) -> Resource { + src.implement( + |req, me| { + let data: &Mutex = me.user_data().unwrap(); + let mut guard = data.lock().unwrap(); + match req { + Request::Offer { mime_type } => guard.mime_types.push(mime_type), + Request::SetActions { dnd_actions } => { + guard.dnd_action = DndAction::from_bits_truncate(dnd_actions); + } + Request::Destroy => {} + } + }, + None::, + Mutex::new(SourceMetadata { + mime_types: Vec::new(), + dnd_action: DndAction::None, + }), + ) +} + +/// Access the metadata of a data source +pub fn with_source_metadata T>( + source: &Resource, + f: F, +) -> Result { + match source.user_data::>() { + Some(data) => Ok(f(&data.lock().unwrap())), + None => Err(()), + } +} diff --git a/src/wayland/data_device/mod.rs b/src/wayland/data_device/mod.rs new file mode 100644 index 0000000..9cab6dd --- /dev/null +++ b/src/wayland/data_device/mod.rs @@ -0,0 +1,282 @@ +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, + }, + Client, Display, Global, NewResource, Resource, +}; + +use wayland::seat::{AxisFrame, PointerGrab, PointerInnerHandle, Seat}; + +mod data_source; + +pub use self::data_source::{with_source_metadata, SourceMetadata}; + +enum Selection { + Empty, + Client(Resource), +} + +struct SeatData { + known_devices: Vec>, + selection: Selection, + log: ::slog::Logger, + current_focus: Option, +} + +impl SeatData { + fn set_selection(&mut self, new_selection: Selection) { + self.selection = new_selection; + self.send_selection(); + } + + fn set_focus(&mut self, new_focus: Option) { + self.current_focus = new_focus; + self.send_selection(); + } + + fn send_selection(&mut self) { + let client = match self.current_focus.as_ref() { + Some(c) => c, + None => return, + }; + // first sanitize the selection, reseting it to null if the client holding + // it dropped it + let cleanup = if let Selection::Client(ref data_source) = self.selection { + !data_source.is_alive() + } else { + false + }; + if cleanup { + self.selection = Selection::Empty; + } + // then send it if appropriate + match self.selection { + Selection::Empty => { + // send an empty selection + for dd in &self.known_devices { + // skip data devices not belonging to our client + if dd.client().map(|c| !c.equals(client)).unwrap_or(true) { + continue; + } + dd.send(wl_data_device::Event::Selection { id: None }); + } + } + Selection::Client(ref data_source) => { + for dd in &self.known_devices { + // skip data devices not belonging to our client + if dd.client().map(|c| !c.equals(client)).unwrap_or(true) { + continue; + } + let source = data_source.clone(); + let log = self.log.clone(); + // create a corresponding data offer + let offer = client + .create_resource::(dd.version()) + .unwrap() + .implement( + move |req, _offer| match req { + wl_data_offer::Request::Receive { fd, mime_type } => { + // 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(); + if !valid { + // deny the receive + debug!(log, "Denying a wl_data_offer.receive with invalid source."); + } else { + source.send(wl_data_source::Event::Send { mime_type, fd }); + } + let _ = ::nix::unistd::close(fd); + } + _ => { /* seleciton data offers only care about the `receive` event */ } + }, + None::, + (), + ); + // advertize the offer to the client + dd.send(wl_data_device::Event::DataOffer { id: offer.clone() }); + with_source_metadata(data_source, |meta| { + for mime_type in meta.mime_types.iter().cloned() { + offer.send(wl_data_offer::Event::Offer { mime_type }) + } + }).unwrap(); + dd.send(wl_data_device::Event::Selection { id: Some(offer) }); + } + } + } + } +} + +impl SeatData { + fn new(log: ::slog::Logger) -> SeatData { + SeatData { + known_devices: Vec::new(), + selection: Selection::Empty, + log, + current_focus: None, + } + } +} + +/// Initialize the data device global +pub fn init_data_device( + display: &mut Display, + logger: L, +) -> Global +where + L: Into>, +{ + let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "data_device_mgr")); + + let global = display.create_global(3, move |new_ddm, _version| { + implement_ddm(new_ddm, log.clone()); + }); + + global +} + +/// Set the data device focus to a certain client for a given seat +pub fn set_data_device_focus(seat: &Seat, client: Option) { + // ensure the seat user_data is ready + // TODO: find a better way to retrieve a logger without requiring the user + // to provide one ? + // This should be a rare path anyway, it is unlikely that a client gets focus + // before initializing its data device, which would already init the user_data. + seat.user_data().insert_if_missing(|| { + Mutex::new(SeatData::new( + seat.arc.log.new(o!("smithay_module" => "data_device_mgr")), + )) + }); + let seat_data = seat.user_data().get::>().unwrap(); + seat_data.lock().unwrap().set_focus(client); +} + +fn implement_ddm( + new_ddm: NewResource, + log: ::slog::Logger, +) -> Resource { + use self::wl_data_device_manager::Request; + new_ddm.implement( + move |req, _ddm| match req { + Request::CreateDataSource { id } => { + self::data_source::implement_data_source(id); + } + Request::GetDataDevice { id, seat } => match Seat::from_resource(&seat) { + Some(seat) => { + // ensure the seat user_data is ready + seat.user_data() + .insert_if_missing(|| Mutex::new(SeatData::new(log.clone()))); + let seat_data = seat.user_data().get::>().unwrap(); + let data_device = implement_data_device(id, seat.clone(), log.clone()); + seat_data.lock().unwrap().known_devices.push(data_device); + } + None => { + error!(log, "Unmanaged seat given to a data device."); + } + }, + }, + None::, + (), + ) +} + +fn implement_data_device( + new_dd: NewResource, + seat: Seat, + log: ::slog::Logger, +) -> Resource { + use self::wl_data_device::Request; + new_dd.implement( + move |req, dd| match req { + Request::StartDrag { + source, + origin, + icon: _, + serial, + } => { + /* TODO: handle the icon */ + 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, + ); + return; + } + } + debug!(log, "denying drag from client without implicit grab"); + } + Request::SetSelection { source, serial: _ } => { + if let Some(keyboard) = seat.get_keyboard() { + if dd + .client() + .as_ref() + .map(|c| keyboard.has_focus(c)) + .unwrap_or(false) + { + let seat_data = seat.user_data().get::>().unwrap(); + // The client has kbd focus, it can set the selection + seat_data + .lock() + .unwrap() + .set_selection(source.map(Selection::Client).unwrap_or(Selection::Empty)); + return; + } + } + debug!(log, "denying setting selection by a non-focused client"); + } + Request::Release => { + // Clean up the known devices + seat.user_data() + .get::>() + .unwrap() + .lock() + .unwrap() + .known_devices + .retain(|ndd| ndd.is_alive() && (!ndd.equals(&dd))) + } + }, + None::, + (), + ) +} + +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/mod.rs b/src/wayland/mod.rs index 1d1fd29..b61ba8d 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -15,6 +15,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; pub mod compositor; +pub mod data_device; pub mod output; pub mod seat; pub mod shell; diff --git a/src/wayland/seat/mod.rs b/src/wayland/seat/mod.rs index 242e472..68a73b0 100644 --- a/src/wayland/seat/mod.rs +++ b/src/wayland/seat/mod.rs @@ -77,10 +77,10 @@ struct Inner { known_seats: Vec>, } -struct SeatArc { +pub(crate) struct SeatArc { inner: Mutex, user_data: UserDataMap, - log: ::slog::Logger, + pub(crate) log: ::slog::Logger, name: String, } @@ -117,7 +117,7 @@ impl Inner { /// See module-level documentation for details of use. #[derive(Clone)] pub struct Seat { - arc: Arc, + pub(crate) arc: Arc, } impl Seat { From 3e28358668d0db6dffad145bc3591416943f4787 Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Tue, 20 Nov 2018 23:27:12 +0100 Subject: [PATCH 06/12] seat: track pressed buttons for the grab --- src/wayland/seat/pointer.rs | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/wayland/seat/pointer.rs b/src/wayland/seat/pointer.rs index ff9f6dc..c8a6951 100644 --- a/src/wayland/seat/pointer.rs +++ b/src/wayland/seat/pointer.rs @@ -20,6 +20,7 @@ struct PointerInternal { focus: Option<(Resource, (f64, f64))>, location: (f64, f64), grab: GrabStatus, + pressed_buttons: Vec, } impl PointerInternal { @@ -29,6 +30,7 @@ impl PointerInternal { focus: None, location: (0.0, 0.0), grab: GrabStatus::None, + pressed_buttons: Vec::new(), } } @@ -146,7 +148,16 @@ impl PointerHandle { /// This will internally send the appropriate button event to the client /// objects matching with the currently focused surface. pub fn button(&self, button: u32, state: ButtonState, serial: u32, time: u32) { - self.inner.lock().unwrap().with_grab(|mut handle, grab| { + let mut inner = self.inner.lock().unwrap(); + match state { + ButtonState::Pressed => { + inner.pressed_buttons.push(button); + } + ButtonState::Released => { + inner.pressed_buttons.retain(|b| *b != button); + } + } + inner.with_grab(|mut handle, grab| { grab.button(&mut handle, button, state, serial, time); }); } @@ -229,6 +240,14 @@ impl<'a> PointerInnerHandle<'a> { self.inner.location } + /// A list of the currently physically pressed buttons + /// + /// This still includes buttons that your grab have intercepted and not sent + /// to the client. + pub fn current_pressed(&self) -> &[u32] { + &self.inner.pressed_buttons + } + /// Notify that the pointer moved /// /// You provide the new location of the pointer, in the form of: @@ -542,7 +561,6 @@ impl PointerGrab for DefaultGrab { handle.set_grab( serial, ClickGrab { - buttons: vec![button], current_focus, pending_focus: None, }, @@ -559,7 +577,6 @@ impl PointerGrab for DefaultGrab { // In case the user maintains several simultaneous clicks, release // the grab once all are released. struct ClickGrab { - buttons: Vec, current_focus: Option<(Resource, (f64, f64))>, pending_focus: Option<(Resource, (f64, f64))>, } @@ -585,12 +602,8 @@ impl PointerGrab for ClickGrab { serial: u32, time: u32, ) { - match state { - ButtonState::Pressed => self.buttons.push(button), - ButtonState::Released => self.buttons.retain(|b| *b != button), - } handle.button(button, state, serial, time); - if self.buttons.is_empty() { + if handle.current_pressed().len() == 0 { // no more buttons are pressed, release the grab handle.unset_grab(); // restore the focus From 81956c8fd0ab17a1d30091c1b126cdc5727c5de1 Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Wed, 21 Nov 2018 01:46:34 +0100 Subject: [PATCH 07/12] data_device: basic DnD support --- anvil/src/shell.rs | 4 +- src/wayland/data_device/dnd_grab.rs | 325 ++++++++++++++++++++++++++++ src/wayland/data_device/mod.rs | 48 +--- src/wayland/seat/pointer.rs | 19 +- 4 files changed, 344 insertions(+), 52 deletions(-) create mode 100644 src/wayland/data_device/dnd_grab.rs 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) { From 35645596a8d752e4d33ad12f69133763307bb0cb Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Wed, 21 Nov 2018 16:01:34 +0100 Subject: [PATCH 08/12] data_device: let the compositor decide the action choice strategy --- anvil/src/main.rs | 1 + anvil/src/misc.rs | 19 +++++++++++ anvil/src/udev.rs | 2 +- anvil/src/winit.rs | 2 +- src/wayland/data_device/dnd_grab.rs | 52 +++++++++++++++++------------ src/wayland/data_device/mod.rs | 46 +++++++++++++++++++------ 6 files changed, 87 insertions(+), 35 deletions(-) create mode 100644 anvil/src/misc.rs diff --git a/anvil/src/main.rs b/anvil/src/main.rs index ce4523e..ee1c95f 100644 --- a/anvil/src/main.rs +++ b/anvil/src/main.rs @@ -16,6 +16,7 @@ use smithay::wayland_server::{calloop::EventLoop, Display}; mod shaders; mod glium_drawer; mod input_handler; +mod misc; #[cfg(feature = "tty_launch")] mod raw_drm; mod shell; diff --git a/anvil/src/misc.rs b/anvil/src/misc.rs new file mode 100644 index 0000000..5d26ee6 --- /dev/null +++ b/anvil/src/misc.rs @@ -0,0 +1,19 @@ +use smithay::wayland_server::protocol::wl_data_device_manager::DndAction; + +pub fn dnd_action_chooser(available: DndAction, preferred: DndAction) -> DndAction { + // if the preferred action is valid (a single action) and in the available actions, use it + // otherwise, follow a fallback stategy + if [DndAction::Move, DndAction::Copy, DndAction::Ask].contains(&preferred) + && available.contains(preferred) + { + preferred + } else if available.contains(DndAction::Ask) { + DndAction::Ask + } else if available.contains(DndAction::Copy) { + DndAction::Copy + } else if available.contains(DndAction::Move) { + DndAction::Move + } else { + DndAction::empty() + } +} diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index 8328c40..39d69f5 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -108,7 +108,7 @@ pub fn run_udev(mut display: Display, mut event_loop: EventLoop<()>, log: Logger let udev_session_id = notifier.register(&mut udev_backend); - init_data_device(&mut display.borrow_mut(), log.clone()); + init_data_device(&mut display.borrow_mut(), ::misc::dnd_action_chooser, log.clone()); let (mut w_seat, _) = Seat::new(&mut display.borrow_mut(), session.seat(), log.clone()); diff --git a/anvil/src/winit.rs b/anvil/src/winit.rs index 8890e88..ecb3ff4 100644 --- a/anvil/src/winit.rs +++ b/anvil/src/winit.rs @@ -54,7 +54,7 @@ pub fn run_winit(display: &mut Display, event_loop: &mut EventLoop<()>, log: Log let (compositor_token, _, _, window_map) = init_shell(display, log.clone()); - init_data_device(display, log.clone()); + init_data_device(display, ::misc::dnd_action_chooser, log.clone()); let (mut seat, _) = Seat::new(display, "winit".into(), log.clone()); diff --git a/src/wayland/data_device/dnd_grab.rs b/src/wayland/data_device/dnd_grab.rs index 3a6bb0f..9cdc683 100644 --- a/src/wayland/data_device/dnd_grab.rs +++ b/src/wayland/data_device/dnd_grab.rs @@ -12,21 +12,23 @@ use wayland::seat::{AxisFrame, PointerGrab, PointerInnerHandle, Seat}; use super::{with_source_metadata, SeatData}; -pub(crate) struct DnDGrab { +pub(crate) struct DnDGrab { data_source: Option>, current_focus: Option>, pending_offers: Vec>, offer_data: Option>>, origin: Resource, seat: Seat, + action_choice: Arc>, } -impl DnDGrab { +impl DnDGrab { pub(crate) fn new( source: Option>, origin: Resource, seat: Seat, - ) -> DnDGrab { + action_choice: Arc>, + ) -> DnDGrab { DnDGrab { data_source: source, current_focus: None, @@ -34,11 +36,15 @@ impl DnDGrab { offer_data: None, origin, seat, + action_choice, } } } -impl PointerGrab for DnDGrab { +impl PointerGrab for DnDGrab +where + F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static, +{ fn motion( &mut self, _handle: &mut PointerInnerHandle, @@ -96,8 +102,14 @@ impl PointerGrab for DnDGrab { // create a data offer let offer = client .create_resource::(device.version()) - .map(|offer| implement_dnd_data_offer(offer, source.clone(), offer_data.clone())) - .unwrap(); + .map(|offer| { + implement_dnd_data_offer( + offer, + source.clone(), + offer_data.clone(), + self.action_choice.clone(), + ) + }).unwrap(); // advertize the offer to the client device.send(wl_data_device::Event::DataOffer { id: offer.clone() }); with_source_metadata(source, |meta| { @@ -221,11 +233,15 @@ struct OfferData { chosen_action: DndAction, } -fn implement_dnd_data_offer( +fn implement_dnd_data_offer( offer: NewResource, source: Resource, offer_data: Arc>, -) -> Resource { + action_choice: Arc>, +) -> Resource +where + F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static, +{ use self::wl_data_offer::Request; offer.implement( move |req, offer| { @@ -296,20 +312,12 @@ fn implement_dnd_data_offer( 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; - } - } + 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(), }); diff --git a/src/wayland/data_device/mod.rs b/src/wayland/data_device/mod.rs index afa03ed..7a15564 100644 --- a/src/wayland/data_device/mod.rs +++ b/src/wayland/data_device/mod.rs @@ -1,7 +1,11 @@ -use std::sync::Mutex; +use std::sync::{Arc, Mutex}; use wayland_server::{ - protocol::{wl_data_device, wl_data_device_manager, wl_data_offer, wl_data_source}, + protocol::{ + wl_data_device, + wl_data_device_manager::{self, DndAction}, + wl_data_offer, wl_data_source, + }, Client, Display, Global, NewResource, Resource, }; @@ -121,17 +125,25 @@ impl SeatData { } /// Initialize the data device global -pub fn init_data_device( +/// +/// You need to provide a `(DndAction, DndAction) -> DndAction` closure that will arbitrate +/// the choice of action resulting from a drag'n'drop session. Its first argument is the set of +/// available actions (which is the intersection of the actions supported by the source and targets) +/// and the second argument is the preferred action reported by the target. If no action should be +/// chosen (and thus the drag'n'drop should abort on drop), return `DndAction::empty()`. +pub fn init_data_device( display: &mut Display, + action_choice: F, logger: L, ) -> Global where + F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static, L: Into>, { let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "data_device_mgr")); - + let action_choice = Arc::new(Mutex::new(action_choice)); let global = display.create_global(3, move |new_ddm, _version| { - implement_ddm(new_ddm, log.clone()); + implement_ddm(new_ddm, action_choice.clone(), log.clone()); }); global @@ -153,10 +165,14 @@ pub fn set_data_device_focus(seat: &Seat, client: Option) { seat_data.lock().unwrap().set_focus(client); } -fn implement_ddm( +fn implement_ddm( new_ddm: NewResource, + action_choice: Arc>, log: ::slog::Logger, -) -> Resource { +) -> Resource +where + F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static, +{ use self::wl_data_device_manager::Request; new_ddm.implement( move |req, _ddm| match req { @@ -169,7 +185,8 @@ fn implement_ddm( seat.user_data() .insert_if_missing(|| Mutex::new(SeatData::new(log.clone()))); let seat_data = seat.user_data().get::>().unwrap(); - let data_device = implement_data_device(id, seat.clone(), log.clone()); + let data_device = + implement_data_device(id, seat.clone(), action_choice.clone(), log.clone()); seat_data.lock().unwrap().known_devices.push(data_device); } None => { @@ -182,11 +199,15 @@ fn implement_ddm( ) } -fn implement_data_device( +fn implement_data_device( new_dd: NewResource, seat: Seat, + action_choice: Arc>, log: ::slog::Logger, -) -> Resource { +) -> Resource +where + F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static, +{ use self::wl_data_device::Request; new_dd.implement( move |req, dd| match req { @@ -200,7 +221,10 @@ 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(dnd_grab::DnDGrab::new(source, origin, seat.clone()), serial); + pointer.set_grab( + dnd_grab::DnDGrab::new(source, origin, seat.clone(), action_choice.clone()), + serial, + ); return; } } From d41517f85b4e8bc98e10d24459587847fe010fb0 Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Wed, 21 Nov 2018 22:24:18 +0100 Subject: [PATCH 09/12] data_device: let the compositor interact with the selection --- anvil/src/udev.rs | 7 +- anvil/src/winit.rs | 2 +- src/wayland/data_device/mod.rs | 113 +++++++++++++++++++++++++++++++-- 3 files changed, 113 insertions(+), 9 deletions(-) diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index 39d69f5..4e6dacd 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -108,7 +108,12 @@ pub fn run_udev(mut display: Display, mut event_loop: EventLoop<()>, log: Logger let udev_session_id = notifier.register(&mut udev_backend); - init_data_device(&mut display.borrow_mut(), ::misc::dnd_action_chooser, log.clone()); + init_data_device( + &mut display.borrow_mut(), + |_| {}, + ::misc::dnd_action_chooser, + log.clone(), + ); let (mut w_seat, _) = Seat::new(&mut display.borrow_mut(), session.seat(), log.clone()); diff --git a/anvil/src/winit.rs b/anvil/src/winit.rs index ecb3ff4..0ef1b76 100644 --- a/anvil/src/winit.rs +++ b/anvil/src/winit.rs @@ -54,7 +54,7 @@ pub fn run_winit(display: &mut Display, event_loop: &mut EventLoop<()>, log: Log let (compositor_token, _, _, window_map) = init_shell(display, log.clone()); - init_data_device(display, ::misc::dnd_action_chooser, log.clone()); + init_data_device(display, |_| {}, ::misc::dnd_action_chooser, log.clone()); let (mut seat, _) = Seat::new(display, "winit".into(), log.clone()); diff --git a/src/wayland/data_device/mod.rs b/src/wayland/data_device/mod.rs index 7a15564..f76731c 100644 --- a/src/wayland/data_device/mod.rs +++ b/src/wayland/data_device/mod.rs @@ -1,3 +1,4 @@ +use std::os::unix::io::RawFd; use std::sync::{Arc, Mutex}; use wayland_server::{ @@ -16,9 +17,25 @@ mod dnd_grab; pub use self::data_source::{with_source_metadata, SourceMetadata}; +/// Events that are generated by interactions of the clients with the data device +pub enum DataDeviceEvent { + /// A client has set the selection + NewSelection(Option>), + /// A client started a drag'n'drop as response to a user pointer action + DnDStarted(Option>), + /// A client requested to read the server-set selection + SendSelection { + // the requested mime type + mime_type: String, + // the fd to write into + fd: RawFd, + }, +} + enum Selection { Empty, Client(Resource), + Compositor(SourceMetadata), } struct SeatData { @@ -109,6 +126,50 @@ impl SeatData { dd.send(wl_data_device::Event::Selection { id: Some(offer) }); } } + Selection::Compositor(ref meta) => { + for dd in &self.known_devices { + // skip data devices not belonging to our client + if dd.client().map(|c| !c.equals(client)).unwrap_or(true) { + continue; + } + let log = self.log.clone(); + let offer_meta = meta.clone(); + let callback = dd + .user_data::>>() + .unwrap() + .clone(); + // create a corresponding data offer + let offer = client + .create_resource::(dd.version()) + .unwrap() + .implement( + move |req, _offer| match req { + wl_data_offer::Request::Receive { fd, mime_type } => { + // check if the associated mime type is valid + if !offer_meta.mime_types.contains(&mime_type) { + // deny the receive + debug!(log, "Denying a wl_data_offer.receive with invalid source."); + let _ = ::nix::unistd::close(fd); + } else { + (&mut *callback.lock().unwrap())(DataDeviceEvent::SendSelection { + mime_type, + fd, + }); + } + } + _ => { /* seleciton data offers only care about the `receive` event */ } + }, + None::, + (), + ); + // advertize the offer to the client + dd.send(wl_data_device::Event::DataOffer { id: offer.clone() }); + for mime_type in meta.mime_types.iter().cloned() { + offer.send(wl_data_offer::Event::Offer { mime_type }) + } + dd.send(wl_data_device::Event::Selection { id: Some(offer) }); + } + } } } } @@ -131,19 +192,22 @@ impl SeatData { /// available actions (which is the intersection of the actions supported by the source and targets) /// and the second argument is the preferred action reported by the target. If no action should be /// chosen (and thus the drag'n'drop should abort on drop), return `DndAction::empty()`. -pub fn init_data_device( +pub fn init_data_device( display: &mut Display, + callback: C, action_choice: F, logger: L, ) -> Global where F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static, + C: FnMut(DataDeviceEvent) + Send + 'static, L: Into>, { let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "data_device_mgr")); let action_choice = Arc::new(Mutex::new(action_choice)); + let callback = Arc::new(Mutex::new(callback)); let global = display.create_global(3, move |new_ddm, _version| { - implement_ddm(new_ddm, action_choice.clone(), log.clone()); + implement_ddm(new_ddm, callback.clone(), action_choice.clone(), log.clone()); }); global @@ -165,13 +229,38 @@ pub fn set_data_device_focus(seat: &Seat, client: Option) { seat_data.lock().unwrap().set_focus(client); } -fn implement_ddm( +/// Set a compositor-provided selection for this seat +/// +/// You need to provide the available mime types for this selection. +/// +/// Whenever a client requests to read the selection, your callback will +/// receive a `DataDeviceEvent::SendSelection` event. +pub fn set_data_device_selection(seat: &Seat, mime_types: Vec) { + // 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")), + )) + }); + let seat_data = seat.user_data().get::>().unwrap(); + seat_data + .lock() + .unwrap() + .set_selection(Selection::Compositor(SourceMetadata { + mime_types, + dnd_action: DndAction::empty(), + })); +} + +fn implement_ddm( new_ddm: NewResource, + callback: Arc>, action_choice: Arc>, log: ::slog::Logger, ) -> Resource where F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static, + C: FnMut(DataDeviceEvent) + Send + 'static, { use self::wl_data_device_manager::Request; new_ddm.implement( @@ -185,8 +274,13 @@ where seat.user_data() .insert_if_missing(|| Mutex::new(SeatData::new(log.clone()))); let seat_data = seat.user_data().get::>().unwrap(); - let data_device = - implement_data_device(id, seat.clone(), action_choice.clone(), log.clone()); + let data_device = implement_data_device( + id, + seat.clone(), + callback.clone(), + action_choice.clone(), + log.clone(), + ); seat_data.lock().unwrap().known_devices.push(data_device); } None => { @@ -199,16 +293,19 @@ where ) } -fn implement_data_device( +fn implement_data_device( new_dd: NewResource, seat: Seat, + callback: Arc>, action_choice: Arc>, log: ::slog::Logger, ) -> Resource where F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static, + C: FnMut(DataDeviceEvent) + Send + 'static, { use self::wl_data_device::Request; + let callback2 = callback.clone(); new_dd.implement( move |req, dd| match req { Request::StartDrag { @@ -221,6 +318,7 @@ where 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 + (&mut *callback.lock().unwrap())(DataDeviceEvent::DnDStarted(source.clone())); pointer.set_grab( dnd_grab::DnDGrab::new(source, origin, seat.clone(), action_choice.clone()), serial, @@ -239,6 +337,7 @@ where .unwrap_or(false) { let seat_data = seat.user_data().get::>().unwrap(); + (&mut *callback.lock().unwrap())(DataDeviceEvent::NewSelection(source.clone())); // The client has kbd focus, it can set the selection seat_data .lock() @@ -261,6 +360,6 @@ where } }, None::, - (), + callback2 as Arc>, ) } From 41f1b37834e1ac65e5d76fb2c300423cbd1163df Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Wed, 21 Nov 2018 23:40:25 +0100 Subject: [PATCH 10/12] data_device: let the compositor generate DnD grabs --- src/wayland/data_device/dnd_grab.rs | 32 +-- src/wayland/data_device/mod.rs | 51 +++- src/wayland/data_device/server_dnd_grab.rs | 320 +++++++++++++++++++++ 3 files changed, 373 insertions(+), 30 deletions(-) create mode 100644 src/wayland/data_device/server_dnd_grab.rs diff --git a/src/wayland/data_device/dnd_grab.rs b/src/wayland/data_device/dnd_grab.rs index 9cdc683..4876f14 100644 --- a/src/wayland/data_device/dnd_grab.rs +++ b/src/wayland/data_device/dnd_grab.rs @@ -10,25 +10,23 @@ use wayland_server::{ use wayland::seat::{AxisFrame, PointerGrab, PointerInnerHandle, Seat}; -use super::{with_source_metadata, SeatData}; +use super::{with_source_metadata, DataDeviceData, SeatData}; -pub(crate) struct DnDGrab { +pub(crate) struct DnDGrab { data_source: Option>, current_focus: Option>, pending_offers: Vec>, offer_data: Option>>, origin: Resource, seat: Seat, - action_choice: Arc>, } -impl DnDGrab { +impl DnDGrab { pub(crate) fn new( source: Option>, origin: Resource, seat: Seat, - action_choice: Arc>, - ) -> DnDGrab { + ) -> DnDGrab { DnDGrab { data_source: source, current_focus: None, @@ -36,15 +34,11 @@ impl DnDGrab { offer_data: None, origin, seat, - action_choice, } } } -impl PointerGrab for DnDGrab -where - F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static, -{ +impl PointerGrab for DnDGrab { fn motion( &mut self, _handle: &mut PointerInnerHandle, @@ -99,6 +93,11 @@ where .iter() .filter(|d| d.same_client_as(&surface)) { + let action_choice = device + .user_data::() + .unwrap() + .action_choice + .clone(); // create a data offer let offer = client .create_resource::(device.version()) @@ -107,7 +106,7 @@ where offer, source.clone(), offer_data.clone(), - self.action_choice.clone(), + action_choice, ) }).unwrap(); // advertize the offer to the client @@ -233,15 +232,12 @@ struct OfferData { chosen_action: DndAction, } -fn implement_dnd_data_offer( +fn implement_dnd_data_offer( offer: NewResource, source: Resource, offer_data: Arc>, - action_choice: Arc>, -) -> Resource -where - F: FnMut(DndAction, DndAction) -> DndAction + Send + 'static, -{ + action_choice: Arc DndAction + Send + 'static>>, +) -> Resource { use self::wl_data_offer::Request; offer.implement( move |req, offer| { diff --git a/src/wayland/data_device/mod.rs b/src/wayland/data_device/mod.rs index f76731c..ade4d5e 100644 --- a/src/wayland/data_device/mod.rs +++ b/src/wayland/data_device/mod.rs @@ -14,8 +14,10 @@ use wayland::seat::Seat; mod data_source; mod dnd_grab; +mod server_dnd_grab; 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 pub enum DataDeviceEvent { @@ -25,9 +27,9 @@ pub enum DataDeviceEvent { DnDStarted(Option>), /// A client requested to read the server-set selection SendSelection { - // the requested mime type + /// the requested mime type mime_type: String, - // the fd to write into + /// the fd to write into fd: RawFd, }, } @@ -134,10 +136,7 @@ impl SeatData { } let log = self.log.clone(); let offer_meta = meta.clone(); - let callback = dd - .user_data::>>() - .unwrap() - .clone(); + let callback = dd.user_data::().unwrap().callback.clone(); // create a corresponding data offer let offer = client .create_resource::(dd.version()) @@ -252,6 +251,29 @@ pub fn set_data_device_selection(seat: &Seat, mime_types: Vec) { })); } +/// 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(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( new_ddm: NewResource, callback: Arc>, @@ -293,6 +315,11 @@ where ) } +struct DataDeviceData { + callback: Arc>, + action_choice: Arc DndAction + Send + 'static>>, +} + fn implement_data_device( new_dd: NewResource, seat: Seat, @@ -305,7 +332,10 @@ where C: FnMut(DataDeviceEvent) + Send + 'static, { use self::wl_data_device::Request; - let callback2 = callback.clone(); + let dd_data = DataDeviceData { + callback: callback.clone(), + action_choice, + }; new_dd.implement( move |req, dd| match req { Request::StartDrag { @@ -319,10 +349,7 @@ where if pointer.has_grab(serial) { // The StartDrag is in response to a pointer implicit grab, all is good (&mut *callback.lock().unwrap())(DataDeviceEvent::DnDStarted(source.clone())); - pointer.set_grab( - dnd_grab::DnDGrab::new(source, origin, seat.clone(), action_choice.clone()), - serial, - ); + pointer.set_grab(dnd_grab::DnDGrab::new(source, origin, seat.clone()), serial); return; } } @@ -360,6 +387,6 @@ where } }, None::, - callback2 as Arc>, + dd_data, ) } diff --git a/src/wayland/data_device/server_dnd_grab.rs b/src/wayland/data_device/server_dnd_grab.rs new file mode 100644 index 0000000..ec8e474 --- /dev/null +++ b/src/wayland/data_device/server_dnd_grab.rs @@ -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 { + metadata: super::SourceMetadata, + current_focus: Option>, + pending_offers: Vec>, + offer_data: Option>>, + seat: Seat, + callback: Arc>, +} + +impl ServerDnDGrab { + pub(crate) fn new( + metadata: super::SourceMetadata, + seat: Seat, + callback: Arc>, + ) -> ServerDnDGrab { + ServerDnDGrab { + metadata, + current_focus: None, + pending_offers: Vec::with_capacity(1), + offer_data: None, + seat, + callback, + } + } +} + +impl PointerGrab for ServerDnDGrab +where + C: FnMut(ServerDndEvent) + Send + 'static, +{ + 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() { + 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::() + .unwrap() + .action_choice + .clone(); + // create a data offer + let offer = client + .create_resource::(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::>() + .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( + offer: NewResource, + metadata: super::SourceMetadata, + offer_data: Arc>, + callback: Arc>, + action_choice: Arc DndAction + Send + 'static>>, +) -> Resource +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::, + (), + ) +} From cc80233318d20995cf0fd29cffc5f6344677ce6c Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Wed, 21 Nov 2018 23:55:06 +0100 Subject: [PATCH 11/12] data_device: module-level docs --- src/wayland/data_device/mod.rs | 37 ++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/wayland/data_device/mod.rs b/src/wayland/data_device/mod.rs index ade4d5e..4f94281 100644 --- a/src/wayland/data_device/mod.rs +++ b/src/wayland/data_device/mod.rs @@ -1,3 +1,29 @@ +//! Utilities for manipulating the data devices +//! +//! The data device is wayland's abstraction to represent both selection (copy/paste) and +//! drag'n'drop actions. This module provides logic to handle this part of the protocol. +//! Selection and drag'n'drop are per-seat notions. +//! +//! This module provides 2 main freestanding functions: +//! +//! - `init_data_device`: this function must be called during the compositor startup to initialize +//! the data device logic +//! - `set_data_device_focus`: this function sets the data device focus for a given seat; you'd +//! typically call it whenever the keyboard focus changes, to follow it (for example in the focus +//! hook of your keyboards) +//! +//! Using these two functions is enough for your clients to be able to interact with each other using +//! the data devices. +//! +//! The module also provides additionnal mechanisms allowing your compositor to see and interact with +//! the contents of the data device: +//! +//! - You can provide a callback closure to `init_data_device` to peek into the the actions of your clients +//! - the freestanding function `set_data_device_selection` allows you to set the contents of the selection +//! for your clients +//! - the freestanding function `start_dnd` allows you to initiate a drag'n'drop event from the compositor +//! itself and receive interactions of clients with it via an other dedicated callback. + use std::os::unix::io::RawFd; use std::sync::{Arc, Mutex}; @@ -186,7 +212,13 @@ impl SeatData { /// Initialize the data device global /// -/// You need to provide a `(DndAction, DndAction) -> DndAction` closure that will arbitrate +/// You can provide a callback to peek into the actions of your clients over the data devices +/// (allowing you to retrieve the current selection buffer, or intercept DnD data). See the +/// `DataDeviceEvent` type for details about what notifications you can receive. Note that this +/// closure will not receive notifications about dnd actions the compositor initiated, see +/// `start_dnd` for details about that. +/// +/// You also need to provide a `(DndAction, DndAction) -> DndAction` closure that will arbitrate /// the choice of action resulting from a drag'n'drop session. Its first argument is the set of /// available actions (which is the intersection of the actions supported by the source and targets) /// and the second argument is the preferred action reported by the target. If no action should be @@ -254,7 +286,8 @@ pub fn set_data_device_selection(seat: &Seat, mime_types: Vec) { /// 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. +/// drag'n'drop in the provided callback. See `SeverDndEvent` for details about +/// which events can be generated and what response is expected from you to them. pub fn start_dnd(seat: &Seat, serial: u32, metadata: SourceMetadata, callback: C) where C: FnMut(ServerDndEvent) + Send + 'static, From ed00fb1d475829e2057d9ca2af4f536c86e3ec21 Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Thu, 22 Nov 2018 15:37:31 +0100 Subject: [PATCH 12/12] data_device: move default_action_chooser into smithay --- anvil/src/main.rs | 1 - anvil/src/misc.rs | 19 --------------- anvil/src/udev.rs | 4 ++-- anvil/src/winit.rs | 4 ++-- src/wayland/data_device/mod.rs | 43 ++++++++++++++++++++++++++++++++++ 5 files changed, 47 insertions(+), 24 deletions(-) diff --git a/anvil/src/main.rs b/anvil/src/main.rs index ee1c95f..ce4523e 100644 --- a/anvil/src/main.rs +++ b/anvil/src/main.rs @@ -16,7 +16,6 @@ use smithay::wayland_server::{calloop::EventLoop, Display}; mod shaders; mod glium_drawer; mod input_handler; -mod misc; #[cfg(feature = "tty_launch")] mod raw_drm; mod shell; diff --git a/anvil/src/misc.rs b/anvil/src/misc.rs index 5d26ee6..e69de29 100644 --- a/anvil/src/misc.rs +++ b/anvil/src/misc.rs @@ -1,19 +0,0 @@ -use smithay::wayland_server::protocol::wl_data_device_manager::DndAction; - -pub fn dnd_action_chooser(available: DndAction, preferred: DndAction) -> DndAction { - // if the preferred action is valid (a single action) and in the available actions, use it - // otherwise, follow a fallback stategy - if [DndAction::Move, DndAction::Copy, DndAction::Ask].contains(&preferred) - && available.contains(preferred) - { - preferred - } else if available.contains(DndAction::Ask) { - DndAction::Ask - } else if available.contains(DndAction::Copy) { - DndAction::Copy - } else if available.contains(DndAction::Move) { - DndAction::Move - } else { - DndAction::empty() - } -} diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index 4e6dacd..a3de2aa 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -42,7 +42,7 @@ use smithay::{ input::Libinput, wayland::{ compositor::CompositorToken, - data_device::{init_data_device, set_data_device_focus}, + data_device::{default_action_chooser, init_data_device, set_data_device_focus}, output::{Mode, Output, PhysicalProperties}, seat::{Seat, XkbConfig}, shm::init_shm_global, @@ -111,7 +111,7 @@ pub fn run_udev(mut display: Display, mut event_loop: EventLoop<()>, log: Logger init_data_device( &mut display.borrow_mut(), |_| {}, - ::misc::dnd_action_chooser, + default_action_chooser, log.clone(), ); diff --git a/anvil/src/winit.rs b/anvil/src/winit.rs index 0ef1b76..4d191bd 100644 --- a/anvil/src/winit.rs +++ b/anvil/src/winit.rs @@ -11,7 +11,7 @@ use smithay::{ winit, }, wayland::{ - data_device::{init_data_device, set_data_device_focus}, + data_device::{default_action_chooser, init_data_device, set_data_device_focus}, output::{Mode, Output, PhysicalProperties}, seat::{Seat, XkbConfig}, shm::init_shm_global, @@ -54,7 +54,7 @@ pub fn run_winit(display: &mut Display, event_loop: &mut EventLoop<()>, log: Log let (compositor_token, _, _, window_map) = init_shell(display, log.clone()); - init_data_device(display, |_| {}, ::misc::dnd_action_chooser, log.clone()); + init_data_device(display, |_| {}, default_action_chooser, log.clone()); let (mut seat, _) = Seat::new(display, "winit".into(), log.clone()); diff --git a/src/wayland/data_device/mod.rs b/src/wayland/data_device/mod.rs index 4f94281..0c2b84b 100644 --- a/src/wayland/data_device/mod.rs +++ b/src/wayland/data_device/mod.rs @@ -23,6 +23,27 @@ //! for your clients //! - the freestanding function `start_dnd` allows you to initiate a drag'n'drop event from the compositor //! itself and receive interactions of clients with it via an other dedicated callback. +//! +//! ## Initialization +//! +//! ``` +//! # extern crate wayland_server; +//! # #[macro_use] extern crate smithay; +//! use smithay::wayland::data_device::{init_data_device, default_action_chooser}; +//! +//! # fn main(){ +//! # let mut event_loop = wayland_server::calloop::EventLoop::<()>::new().unwrap(); +//! # let mut display = wayland_server::Display::new(event_loop.handle()); +//! // init the data device: +//! init_data_device( +//! &mut display, // the display +//! |dnd_event| { /* a callback to react to client DnD/selection actions */ }, +//! default_action_chooser, // a closure to choose the DnD action depending on clients +//! // negociation +//! None // insert a logger here +//! ); +//! # } +//! ``` use std::os::unix::io::RawFd; use std::sync::{Arc, Mutex}; @@ -423,3 +444,25 @@ where dd_data, ) } + +/// A simple action chooser for DnD negociation +/// +/// If the preferred action is available, it'll pick it. Otherwise, it'll pick the first +/// available in the following order: Ask, Copy, Move. +pub fn default_action_chooser(available: DndAction, preferred: DndAction) -> DndAction { + // if the preferred action is valid (a single action) and in the available actions, use it + // otherwise, follow a fallback stategy + if [DndAction::Move, DndAction::Copy, DndAction::Ask].contains(&preferred) + && available.contains(preferred) + { + preferred + } else if available.contains(DndAction::Ask) { + DndAction::Ask + } else if available.contains(DndAction::Copy) { + DndAction::Copy + } else if available.contains(DndAction::Move) { + DndAction::Move + } else { + DndAction::empty() + } +}