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 {