diff --git a/examples/window.rs b/examples/window.rs index 3d87e2f8d9..350153ed1b 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -540,12 +540,18 @@ impl ApplicationHandler for Application { WindowEvent::DoubleTapGesture { .. } => { info!("Smart zoom"); }, + WindowEvent::HoveredFile(path) => { + info!("File hover initiated: {path:?}"); + }, + WindowEvent::HoveredFileCancelled => { + info!("File hover canceled"); + }, + WindowEvent::DroppedFile(path) => { + info!("File dropped: {path:?}"); + }, WindowEvent::TouchpadPressure { .. } - | WindowEvent::HoveredFileCancelled | WindowEvent::KeyboardInput { .. } | WindowEvent::PointerEntered { .. } - | WindowEvent::DroppedFile(_) - | WindowEvent::HoveredFile(_) | WindowEvent::Destroyed | WindowEvent::Moved(_) => (), } diff --git a/src/platform_impl/linux/wayland/seat/data_device/mod.rs b/src/platform_impl/linux/wayland/seat/data_device/mod.rs new file mode 100644 index 0000000000..ae909620ba --- /dev/null +++ b/src/platform_impl/linux/wayland/seat/data_device/mod.rs @@ -0,0 +1,180 @@ +use std::path::PathBuf; + +use ahash::{AHashMap, RandomState}; +use sctk::data_device_manager::{ + data_device::DataDeviceHandler, + data_offer::{DataOfferHandler, DragOffer}, + data_source::DataSourceHandler, WritePipe, +}; +use sctk::reexports::client::protocol::wl_data_source::WlDataSource; +use sctk::reexports::client::protocol::wl_data_device::WlDataDevice; +use sctk::reexports::client::protocol::wl_data_device_manager::DndAction; +use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::reexports::client::{Connection, QueueHandle}; + +use crate::event::WindowEvent; +use crate::platform_impl::wayland::seat::ObjectId; +use crate::platform_impl::wayland::seat::WinitSeatState; +use crate::platform_impl::wayland::state::WinitState; +use crate::platform_impl::wayland; + +fn seat_from_wl_data_device<'a>(seats: &'a mut AHashMap, wl_data_device: &WlDataDevice) -> Option<&'a mut WinitSeatState> { + let Some((_, seat_state )) = seats.iter_mut().find(|(_, seat_state)| match &seat_state.data_device { + Some(data_device) => data_device.inner() == wl_data_device, + None => false + }) else { + return None; + }; + return Some(seat_state); +} + +impl DataDeviceHandler for WinitState { + fn enter( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + wl_data_device: &WlDataDevice, + _x: f64, + _y: f64, + wl_surface: &WlSurface, + ) { + let Some(seat_state) = seat_from_wl_data_device(&mut self.seats, wl_data_device) else { + return; + }; + + let Some(data_device) = &seat_state.data_device else { + return; + }; + + let Some(drag_offer) = data_device.data().drag_offer() else { + return; + }; + + // Accept any mime type, the API consumer can figure this out + let mime_type = drag_offer.with_mime_types(|mime_types| { + if let Some(mime) = mime_types.get(0) { + Some(mime.clone()) + } else { + None + } + }); + drag_offer.accept_mime_type(0, mime_type.clone()); + + drag_offer.set_actions(DndAction::Copy, DndAction::Copy); + + let window_id = wayland::make_wid(&wl_surface); + seat_state.hovered_window = Some(window_id); + self.events_sink.push_window_event(WindowEvent::HoveredFile(PathBuf::new()), window_id); + } + + fn leave(&mut self, _conn: &Connection, _qh: &QueueHandle, wl_data_device: &WlDataDevice) { + let Some(seat_state) = seat_from_wl_data_device(&mut self.seats, wl_data_device) else { + return; + }; + if let Some(window_id) = seat_state.hovered_window { + seat_state.hovered_window = None; + self.events_sink.push_window_event(WindowEvent::HoveredFileCancelled, window_id); + } + } + + fn motion( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _wl_data_device: &WlDataDevice, + _x: f64, + _y: f64, + ) { + + } + + fn selection(&mut self, _conn: &Connection, _qh: &QueueHandle, _wl_data_device: &WlDataDevice) { + + } + + fn drop_performed( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + wl_data_device: &WlDataDevice, + ) { + + let Some(seat_state) = seat_from_wl_data_device(&mut self.seats, wl_data_device) else { + return; + }; + if let Some(window_id) = seat_state.hovered_window { + seat_state.hovered_window = None; + self.events_sink.push_window_event(WindowEvent::DroppedFile(PathBuf::new()), window_id); + } + } +} + +// Inbound +impl DataOfferHandler for WinitState { + fn source_actions( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + offer: &mut DragOffer, + _actions: DndAction, + ) { + offer.set_actions(DndAction::Copy, DndAction::Copy); + } + + fn selected_action( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _offer: &mut DragOffer, + _actions: DndAction, + ) { + + } +} + +// Outbound +impl DataSourceHandler for WinitState { + fn accept_mime( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _wl_data_source: &WlDataSource, + _mime: Option, + ) { + + } + + fn send_request( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _wl_data_source: &WlDataSource, + _mime: String, + _fd: WritePipe, + ) { + + } + + fn cancelled(&mut self, _conn: &Connection, _qh: &QueueHandle, _wl_data_source: &WlDataSource) { + + } + + fn dnd_dropped(&mut self, _conn: &Connection, _qh: &QueueHandle, _wl_data_source: &WlDataSource) { + + + } + + fn dnd_finished(&mut self, _conn: &Connection, _qh: &QueueHandle, _wl_data_source: &WlDataSource) { + + } + + fn action( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _wl_data_source: &WlDataSource, + _action: DndAction, + ) { + + } +} diff --git a/src/platform_impl/linux/wayland/seat/mod.rs b/src/platform_impl/linux/wayland/seat/mod.rs index 1306699029..b3c94b5c0d 100644 --- a/src/platform_impl/linux/wayland/seat/mod.rs +++ b/src/platform_impl/linux/wayland/seat/mod.rs @@ -9,6 +9,7 @@ use sctk::reexports::client::protocol::wl_touch::WlTouch; use sctk::reexports::client::{Connection, Proxy, QueueHandle}; use sctk::reexports::protocols::wp::relative_pointer::zv1::client::zwp_relative_pointer_v1::ZwpRelativePointerV1; use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3; +use sctk::data_device_manager::data_device::DataDevice; use sctk::seat::pointer::{ThemeSpec, ThemedPointer}; use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState}; use tracing::warn; @@ -16,7 +17,9 @@ use tracing::warn; use crate::event::WindowEvent; use crate::keyboard::ModifiersState; use crate::platform_impl::wayland::state::WinitState; +use crate::window::WindowId; +mod data_device; mod keyboard; mod pointer; mod text_input; @@ -57,11 +60,20 @@ pub struct WinitSeatState { /// Whether we have pending modifiers. modifiers_pending: bool, + + /// Copy & Paste / Drag & Drop data + pub data_device: Option, + + /// Hold id of hoevered drop window + pub hovered_window: Option, } impl WinitSeatState { - pub fn new() -> Self { - Default::default() + pub fn new(data_device: DataDevice) -> Self { + return Self { + data_device: Some(data_device), + ..Default::default() + } } } @@ -198,10 +210,12 @@ impl SeatHandler for WinitState { fn new_seat( &mut self, _connection: &Connection, - _queue_handle: &QueueHandle, + queue_handle: &QueueHandle, seat: WlSeat, ) { - self.seats.insert(seat.id(), WinitSeatState::new()); + let data_device_manager = &self.data_device_manager_state; + let data_device = data_device_manager.get_data_device(queue_handle, &seat); + self.seats.insert(seat.id(), WinitSeatState::new(data_device)); } fn remove_seat( diff --git a/src/platform_impl/linux/wayland/state.rs b/src/platform_impl/linux/wayland/state.rs index bec0a55d3a..c8648c03fe 100644 --- a/src/platform_impl/linux/wayland/state.rs +++ b/src/platform_impl/linux/wayland/state.rs @@ -4,6 +4,7 @@ use std::sync::{Arc, Mutex}; use ahash::AHashMap; use sctk::compositor::{CompositorHandler, CompositorState}; +use sctk::data_device_manager::DataDeviceManagerState; use sctk::output::{OutputHandler, OutputState}; use sctk::reexports::calloop::LoopHandle; use sctk::reexports::client::backend::ObjectId; @@ -116,6 +117,9 @@ pub struct WinitState { /// Whether the user initiated a wake up. pub proxy_wake_up: bool, + + /// Data device manager handles copy & paste and drag & drop + pub data_device_manager_state: DataDeviceManagerState, } impl WinitState { @@ -142,11 +146,12 @@ impl WinitState { let output_state = OutputState::new(globals, queue_handle); let monitors = output_state.outputs().map(MonitorHandle::new).collect(); + let data_device_manager_state = DataDeviceManagerState::bind(globals, queue_handle).map_err(|err| os_error!(err))?; let seat_state = SeatState::new(globals, queue_handle); - let mut seats = AHashMap::default(); for seat in seat_state.seats() { - seats.insert(seat.id(), WinitSeatState::new()); + let data_device = data_device_manager_state.get_data_device(queue_handle, &seat); + seats.insert(seat.id(), WinitSeatState::new(data_device)); } let (viewporter_state, fractional_scaling_manager) = @@ -194,6 +199,8 @@ impl WinitState { // Make it true by default. dispatched_events: true, proxy_wake_up: false, + + data_device_manager_state, }) } @@ -435,3 +442,4 @@ sctk::delegate_registry!(WinitState); sctk::delegate_shm!(WinitState); sctk::delegate_xdg_shell!(WinitState); sctk::delegate_xdg_window!(WinitState); +sctk::delegate_data_device!(WinitState);