From ce5657ed1936eb448b0d486fb59cae834291f7f2 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 4 Jun 2025 19:53:41 +0200 Subject: [PATCH 1/5] feat(wayland): add support for file window events --- Cargo.toml | 1 + src/changelog/unreleased.md | 1 + .../linux/wayland/seat/data_device/mod.rs | 199 ++++++++++++++++++ src/platform_impl/linux/wayland/seat/mod.rs | 50 +++++ src/platform_impl/linux/wayland/state.rs | 13 ++ src/platform_impl/linux/wayland/types/dnd.rs | 8 + src/platform_impl/linux/wayland/types/mod.rs | 1 + 7 files changed, 273 insertions(+) create mode 100644 src/platform_impl/linux/wayland/seat/data_device/mod.rs create mode 100644 src/platform_impl/linux/wayland/types/dnd.rs diff --git a/Cargo.toml b/Cargo.toml index b914b5e411..79ceb9d04b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,6 +97,7 @@ rwh_06 = { package = "raw-window-handle", version = "0.6", features = [ serde = { workspace = true, optional = true } smol_str = "0.2.0" tracing = { version = "0.1.40", default-features = false } +url = "2.5.4" [dev-dependencies] image = { version = "0.25.0", default-features = false, features = ["png"] } diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index f3a0f6d2c3..b7b249b382 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -16,6 +16,7 @@ on how to add them: - On X11, add `Window::even_more_rare_api`. - On Wayland, add `Window::common_api`. - On Windows, add `Window::some_rare_api`. +- On Wayland, add support for `DroppedFile`, `HoveredFile` and `HoveredFileCancelled` window events. ``` When the change requires non-trivial amount of work for users to comply 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..0a4971a5dc --- /dev/null +++ b/src/platform_impl/linux/wayland/seat/data_device/mod.rs @@ -0,0 +1,199 @@ +use sctk::data_device_manager::{ + data_device::DataDeviceHandler, + data_offer::{DataOfferHandler, DragOffer}, + data_source::DataSourceHandler, + WritePipe, +}; +use wayland_client::{ + protocol::{ + wl_data_device::WlDataDevice, wl_data_device_manager::DndAction, + wl_data_source::WlDataSource, wl_surface::WlSurface, + }, + Connection, Proxy, QueueHandle, +}; + +use crate::{ + event::WindowEvent, + platform_impl::wayland::{self, state::WinitState, types::dnd::DndOfferState}, +}; + +const SUPPORTED_MIME_TYPES: &[&str] = &["text/uri-list"]; + +fn filter_mime(mime_types: &[String]) -> Option { + for mime in mime_types { + if SUPPORTED_MIME_TYPES.contains(&mime.as_str()) { + return Some(mime.clone()); + } + } + + None +} + +impl DataDeviceHandler for WinitState { + fn enter( + &mut self, + _: &Connection, + _: &QueueHandle, + wl_data_device: &WlDataDevice, + _: f64, + _: f64, + _: &WlSurface, + ) { + let data_device = self.get_data_device(wl_data_device); + + if let Some(data_device) = data_device { + if let Some(offer) = data_device.data().drag_offer() { + if let Some(mime_type) = offer.with_mime_types(filter_mime) { + offer.accept_mime_type(offer.serial, Some(mime_type.clone())); + offer.set_actions(DndAction::Copy, DndAction::Copy); + + if let Ok(read_pipe) = offer.receive(mime_type) { + let surface_id = offer.surface.id(); + let current_offer = offer.clone(); + let window_id = wayland::make_wid(&offer.surface); + + self.read_file_paths(read_pipe, move |state, path| { + state.dnd_offers.insert( + surface_id.clone(), + DndOfferState { + offer: current_offer.clone(), + file_path: path.clone(), + }, + ); + + state + .events_sink + .push_window_event(WindowEvent::HoveredFile(path), window_id); + }); + } + } + } + } + } + + fn leave(&mut self, _: &Connection, _: &QueueHandle, wl_data_device: &WlDataDevice) { + let data_device = self.get_data_device(wl_data_device); + + if let Some(data_device) = data_device { + if let Some(offer) = data_device.data().drag_offer() { + if let Some(dnd) = self.dnd_offers.remove(&offer.surface.id()) { + let window_id = wayland::make_wid(&offer.surface); + self.events_sink + .push_window_event(WindowEvent::HoveredFileCancelled, window_id); + + dnd.offer.destroy(); + } else { + offer.destroy(); + } + } + } + } + + fn motion( + &mut self, + _: &Connection, + _: &QueueHandle, + wl_data_device: &WlDataDevice, + _: f64, + _: f64, + ) { + let data_device = self.get_data_device(wl_data_device); + + if let Some(data_device) = data_device { + if let Some(offer) = data_device.data().drag_offer() { + let surface_id = offer.surface.id(); + let window_id = wayland::make_wid(&offer.surface); + + if let Some(dnd) = self.dnd_offers.get(&surface_id) { + self.events_sink.push_window_event( + WindowEvent::HoveredFile(dnd.file_path.clone()), + window_id, + ); + } + } + } + } + + fn selection(&mut self, _: &Connection, _: &QueueHandle, _: &WlDataDevice) {} + + fn drop_performed( + &mut self, + _: &Connection, + _: &QueueHandle, + wl_data_device: &WlDataDevice, + ) { + let data_device = self.get_data_device(wl_data_device); + + if let Some(data_device) = data_device { + if let Some(offer) = data_device.data().drag_offer() { + let surface_id = offer.surface.id(); + let window_id = wayland::make_wid(&offer.surface); + + if let Some(dnd) = self.dnd_offers.remove(&surface_id) { + self.events_sink.push_window_event( + WindowEvent::DroppedFile(dnd.file_path.clone()), + window_id, + ); + + dnd.offer.finish(); + dnd.offer.destroy(); + } else { + offer.finish(); + offer.destroy(); + } + } + } + } +} + +impl DataSourceHandler for WinitState { + fn accept_mime( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &WlDataSource, + _: Option, + ) { + } + + fn send_request( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &WlDataSource, + _: String, + _: WritePipe, + ) { + } + + fn cancelled(&mut self, _: &Connection, _: &QueueHandle, _: &WlDataSource) {} + + fn dnd_dropped(&mut self, _: &Connection, _: &QueueHandle, _: &WlDataSource) {} + + fn dnd_finished(&mut self, _: &Connection, _: &QueueHandle, _: &WlDataSource) {} + + fn action(&mut self, _: &Connection, _: &QueueHandle, _: &WlDataSource, _: DndAction) {} +} + +impl DataOfferHandler for WinitState { + fn source_actions( + &mut self, + _: &Connection, + _: &QueueHandle, + offer: &mut DragOffer, + _: DndAction, + ) { + offer.set_actions(DndAction::Copy, DndAction::Copy); + } + + fn selected_action( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &mut DragOffer, + _: DndAction, + ) { + } +} + +sctk::delegate_data_device!(WinitState); diff --git a/src/platform_impl/linux/wayland/seat/mod.rs b/src/platform_impl/linux/wayland/seat/mod.rs index eaecd93b33..24ab85af9c 100644 --- a/src/platform_impl/linux/wayland/seat/mod.rs +++ b/src/platform_impl/linux/wayland/seat/mod.rs @@ -1,8 +1,14 @@ //! Seat handling. +use std::fs::File; +use std::io::Read; +use std::path::PathBuf; use std::sync::Arc; use ahash::AHashMap; +use calloop::PostAction; +use sctk::data_device_manager::data_device::DataDevice; +use sctk::data_device_manager::ReadPipe; use tracing::warn; use sctk::reexports::client::backend::ObjectId; @@ -14,11 +20,14 @@ use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3:: use sctk::seat::pointer::{ThemeSpec, ThemedPointer}; use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState}; +use url::Url; +use wayland_client::protocol::wl_data_device::WlDataDevice; use crate::event::WindowEvent; use crate::keyboard::ModifiersState; use crate::platform_impl::wayland::state::WinitState; +mod data_device; mod keyboard; mod pointer; mod text_input; @@ -57,6 +66,8 @@ pub struct WinitSeatState { /// Whether we have pending modifiers. modifiers_pending: bool, + + data_device: Option, } impl WinitSeatState { @@ -141,6 +152,11 @@ impl SeatHandler for WinitState { TextInputData::default(), ))); } + + if seat_state.data_device.is_none() { + let data_device = self.data_device_manager_state.get_data_device(queue_handle, &seat); + seat_state.data_device = Some(data_device); + } } fn remove_capability( @@ -230,6 +246,40 @@ impl WinitState { } } } + + fn get_data_device(&self, wl_data_device: &WlDataDevice) -> Option<&DataDevice> { + self.seats.values().find_map(|seat| { + let data_device = seat.data_device.as_ref()?; + (data_device.inner() == wl_data_device).then_some(data_device) + }) + } + + fn read_file_paths( + &self, + read_pipe: ReadPipe, + callback: F, + ) { + self.loop_handle + .insert_source(read_pipe, move |_, file, state| { + let mut data = String::new(); + + let file: &mut File = unsafe { file.get_mut() }; + if file.read_to_string(&mut data).is_ok() { + if let Some(line) = data.lines().next() { + if let Ok(url) = Url::parse(line) { + if url.scheme() == "file" { + if let Ok(path) = url.to_file_path() { + callback(state, path) + } + } + } + } + } + + PostAction::Remove + }) + .ok(); + } } sctk::delegate_seat!(WinitState); diff --git a/src/platform_impl/linux/wayland/state.rs b/src/platform_impl/linux/wayland/state.rs index 13ef99c26a..d0558197c1 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::data_device_manager::DataDeviceManagerState; use sctk::reexports::calloop::LoopHandle; use sctk::reexports::client::backend::ObjectId; use sctk::reexports::client::globals::GlobalList; @@ -29,6 +30,7 @@ use crate::platform_impl::wayland::seat::{ PointerConstraintsState, RelativePointerState, TextInputState, WinitPointerData, WinitPointerDataExt, WinitSeatState, }; +use crate::platform_impl::wayland::types::dnd::DndOfferState; use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager; use crate::platform_impl::wayland::types::wp_fractional_scaling::FractionalScalingManager; use crate::platform_impl::wayland::types::wp_viewporter::ViewporterState; @@ -54,6 +56,9 @@ pub struct WinitState { /// The seat state responsible for all sorts of input. pub seat_state: SeatState, + pub data_device_manager_state: DataDeviceManagerState, + pub dnd_offers: AHashMap, + /// The shm for software buffers, such as cursors. pub shm: Shm, @@ -141,6 +146,10 @@ 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(WaylandError::Bind)?; + let dnd_offers = AHashMap::default(); + let seat_state = SeatState::new(globals, queue_handle); let mut seats = AHashMap::default(); @@ -164,6 +173,10 @@ impl WinitState { subcompositor_state: subcompositor_state.map(Arc::new), output_state, seat_state, + + data_device_manager_state, + dnd_offers, + shm, custom_cursor_pool, diff --git a/src/platform_impl/linux/wayland/types/dnd.rs b/src/platform_impl/linux/wayland/types/dnd.rs new file mode 100644 index 0000000000..286821fcc6 --- /dev/null +++ b/src/platform_impl/linux/wayland/types/dnd.rs @@ -0,0 +1,8 @@ +use std::path::PathBuf; + +use sctk::data_device_manager::data_offer::DragOffer; + +pub struct DndOfferState { + pub offer: DragOffer, + pub file_path: PathBuf, +} diff --git a/src/platform_impl/linux/wayland/types/mod.rs b/src/platform_impl/linux/wayland/types/mod.rs index 77e67f48be..c9f599c0e0 100644 --- a/src/platform_impl/linux/wayland/types/mod.rs +++ b/src/platform_impl/linux/wayland/types/mod.rs @@ -1,6 +1,7 @@ //! Wayland protocol implementation boilerplate. pub mod cursor; +pub mod dnd; pub mod kwin_blur; pub mod wp_fractional_scaling; pub mod wp_viewporter; From da04ed42c77981b2d26e04a5a7c1364da2a81ef3 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 4 Jun 2025 20:02:22 +0200 Subject: [PATCH 2/5] style(wayland): format --- .../linux/wayland/seat/data_device/mod.rs | 43 ++++++++----------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/src/platform_impl/linux/wayland/seat/data_device/mod.rs b/src/platform_impl/linux/wayland/seat/data_device/mod.rs index 0a4971a5dc..25cc39f99a 100644 --- a/src/platform_impl/linux/wayland/seat/data_device/mod.rs +++ b/src/platform_impl/linux/wayland/seat/data_device/mod.rs @@ -1,21 +1,17 @@ -use sctk::data_device_manager::{ - data_device::DataDeviceHandler, - data_offer::{DataOfferHandler, DragOffer}, - data_source::DataSourceHandler, - WritePipe, -}; -use wayland_client::{ - protocol::{ - wl_data_device::WlDataDevice, wl_data_device_manager::DndAction, - wl_data_source::WlDataSource, wl_surface::WlSurface, - }, - Connection, Proxy, QueueHandle, -}; - -use crate::{ - event::WindowEvent, - platform_impl::wayland::{self, state::WinitState, types::dnd::DndOfferState}, -}; +use sctk::data_device_manager::data_device::DataDeviceHandler; +use sctk::data_device_manager::data_offer::{DataOfferHandler, DragOffer}; +use sctk::data_device_manager::data_source::DataSourceHandler; +use sctk::data_device_manager::WritePipe; +use wayland_client::protocol::wl_data_device::WlDataDevice; +use wayland_client::protocol::wl_data_device_manager::DndAction; +use wayland_client::protocol::wl_data_source::WlDataSource; +use wayland_client::protocol::wl_surface::WlSurface; +use wayland_client::{Connection, Proxy, QueueHandle}; + +use crate::event::WindowEvent; +use crate::platform_impl::wayland::state::WinitState; +use crate::platform_impl::wayland::types::dnd::DndOfferState; +use crate::platform_impl::wayland::{self}; const SUPPORTED_MIME_TYPES: &[&str] = &["text/uri-list"]; @@ -53,13 +49,10 @@ impl DataDeviceHandler for WinitState { let window_id = wayland::make_wid(&offer.surface); self.read_file_paths(read_pipe, move |state, path| { - state.dnd_offers.insert( - surface_id.clone(), - DndOfferState { - offer: current_offer.clone(), - file_path: path.clone(), - }, - ); + state.dnd_offers.insert(surface_id.clone(), DndOfferState { + offer: current_offer.clone(), + file_path: path.clone(), + }); state .events_sink From 8937e185636d3d12296c54881feafe71553d9c0d Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 5 Jun 2025 00:14:38 +0200 Subject: [PATCH 3/5] fix(wayland): HoveredFileCancelled was not dispatched --- .../linux/wayland/seat/data_device/mod.rs | 49 ++++++------------- src/platform_impl/linux/wayland/state.rs | 5 +- src/platform_impl/linux/wayland/types/dnd.rs | 6 +-- 3 files changed, 20 insertions(+), 40 deletions(-) diff --git a/src/platform_impl/linux/wayland/seat/data_device/mod.rs b/src/platform_impl/linux/wayland/seat/data_device/mod.rs index 25cc39f99a..d10c6132ed 100644 --- a/src/platform_impl/linux/wayland/seat/data_device/mod.rs +++ b/src/platform_impl/linux/wayland/seat/data_device/mod.rs @@ -6,7 +6,7 @@ use wayland_client::protocol::wl_data_device::WlDataDevice; use wayland_client::protocol::wl_data_device_manager::DndAction; use wayland_client::protocol::wl_data_source::WlDataSource; use wayland_client::protocol::wl_surface::WlSurface; -use wayland_client::{Connection, Proxy, QueueHandle}; +use wayland_client::{Connection, QueueHandle}; use crate::event::WindowEvent; use crate::platform_impl::wayland::state::WinitState; @@ -44,14 +44,13 @@ impl DataDeviceHandler for WinitState { offer.set_actions(DndAction::Copy, DndAction::Copy); if let Ok(read_pipe) = offer.receive(mime_type) { - let surface_id = offer.surface.id(); - let current_offer = offer.clone(); - let window_id = wayland::make_wid(&offer.surface); + let surface = offer.surface; + let window_id = wayland::make_wid(&surface); self.read_file_paths(read_pipe, move |state, path| { - state.dnd_offers.insert(surface_id.clone(), DndOfferState { - offer: current_offer.clone(), - file_path: path.clone(), + state.dnd_offer = Some(DndOfferState { + surface: surface.clone(), + path: path.clone(), }); state @@ -64,21 +63,10 @@ impl DataDeviceHandler for WinitState { } } - fn leave(&mut self, _: &Connection, _: &QueueHandle, wl_data_device: &WlDataDevice) { - let data_device = self.get_data_device(wl_data_device); - - if let Some(data_device) = data_device { - if let Some(offer) = data_device.data().drag_offer() { - if let Some(dnd) = self.dnd_offers.remove(&offer.surface.id()) { - let window_id = wayland::make_wid(&offer.surface); - self.events_sink - .push_window_event(WindowEvent::HoveredFileCancelled, window_id); - - dnd.offer.destroy(); - } else { - offer.destroy(); - } - } + fn leave(&mut self, _: &Connection, _: &QueueHandle, _: &WlDataDevice) { + if let Some(dnd_offer) = self.dnd_offer.take() { + let window_id = wayland::make_wid(&dnd_offer.surface); + self.events_sink.push_window_event(WindowEvent::HoveredFileCancelled, window_id); } } @@ -94,12 +82,11 @@ impl DataDeviceHandler for WinitState { if let Some(data_device) = data_device { if let Some(offer) = data_device.data().drag_offer() { - let surface_id = offer.surface.id(); let window_id = wayland::make_wid(&offer.surface); - if let Some(dnd) = self.dnd_offers.get(&surface_id) { + if let Some(dnd_offer) = self.dnd_offer.as_ref() { self.events_sink.push_window_event( - WindowEvent::HoveredFile(dnd.file_path.clone()), + WindowEvent::HoveredFile(dnd_offer.path.to_path_buf()), window_id, ); } @@ -119,18 +106,12 @@ impl DataDeviceHandler for WinitState { if let Some(data_device) = data_device { if let Some(offer) = data_device.data().drag_offer() { - let surface_id = offer.surface.id(); let window_id = wayland::make_wid(&offer.surface); - if let Some(dnd) = self.dnd_offers.remove(&surface_id) { - self.events_sink.push_window_event( - WindowEvent::DroppedFile(dnd.file_path.clone()), - window_id, - ); + if let Some(dnd_offer) = self.dnd_offer.take() { + self.events_sink + .push_window_event(WindowEvent::DroppedFile(dnd_offer.path), window_id); - dnd.offer.finish(); - dnd.offer.destroy(); - } else { offer.finish(); offer.destroy(); } diff --git a/src/platform_impl/linux/wayland/state.rs b/src/platform_impl/linux/wayland/state.rs index d0558197c1..febad89ca5 100644 --- a/src/platform_impl/linux/wayland/state.rs +++ b/src/platform_impl/linux/wayland/state.rs @@ -57,7 +57,7 @@ pub struct WinitState { pub seat_state: SeatState, pub data_device_manager_state: DataDeviceManagerState, - pub dnd_offers: AHashMap, + pub dnd_offer: Option, /// The shm for software buffers, such as cursors. pub shm: Shm, @@ -148,7 +148,6 @@ impl WinitState { let data_device_manager_state = DataDeviceManagerState::bind(globals, queue_handle).map_err(WaylandError::Bind)?; - let dnd_offers = AHashMap::default(); let seat_state = SeatState::new(globals, queue_handle); @@ -175,7 +174,7 @@ impl WinitState { seat_state, data_device_manager_state, - dnd_offers, + dnd_offer: None, shm, custom_cursor_pool, diff --git a/src/platform_impl/linux/wayland/types/dnd.rs b/src/platform_impl/linux/wayland/types/dnd.rs index 286821fcc6..eb4b3ea78b 100644 --- a/src/platform_impl/linux/wayland/types/dnd.rs +++ b/src/platform_impl/linux/wayland/types/dnd.rs @@ -1,8 +1,8 @@ use std::path::PathBuf; -use sctk::data_device_manager::data_offer::DragOffer; +use wayland_client::protocol::wl_surface::WlSurface; pub struct DndOfferState { - pub offer: DragOffer, - pub file_path: PathBuf, + pub surface: WlSurface, + pub path: PathBuf, } From a9f61b406bd5a40ff5e08ba06969b7e2e2c75963 Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 5 Jun 2025 00:35:40 +0200 Subject: [PATCH 4/5] fix(wayland): allow for one active dnd offer per device --- .../linux/wayland/seat/data_device/mod.rs | 21 ++++++++++++------- src/platform_impl/linux/wayland/state.rs | 4 ++-- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/platform_impl/linux/wayland/seat/data_device/mod.rs b/src/platform_impl/linux/wayland/seat/data_device/mod.rs index d10c6132ed..4d4742c2f8 100644 --- a/src/platform_impl/linux/wayland/seat/data_device/mod.rs +++ b/src/platform_impl/linux/wayland/seat/data_device/mod.rs @@ -6,7 +6,7 @@ use wayland_client::protocol::wl_data_device::WlDataDevice; use wayland_client::protocol::wl_data_device_manager::DndAction; use wayland_client::protocol::wl_data_source::WlDataSource; use wayland_client::protocol::wl_surface::WlSurface; -use wayland_client::{Connection, QueueHandle}; +use wayland_client::{Connection, Proxy, QueueHandle}; use crate::event::WindowEvent; use crate::platform_impl::wayland::state::WinitState; @@ -44,11 +44,12 @@ impl DataDeviceHandler for WinitState { offer.set_actions(DndAction::Copy, DndAction::Copy); if let Ok(read_pipe) = offer.receive(mime_type) { + let data_device_id = data_device.inner().id(); let surface = offer.surface; let window_id = wayland::make_wid(&surface); self.read_file_paths(read_pipe, move |state, path| { - state.dnd_offer = Some(DndOfferState { + state.dnd_offers.insert(data_device_id.clone(), DndOfferState { surface: surface.clone(), path: path.clone(), }); @@ -63,10 +64,14 @@ impl DataDeviceHandler for WinitState { } } - fn leave(&mut self, _: &Connection, _: &QueueHandle, _: &WlDataDevice) { - if let Some(dnd_offer) = self.dnd_offer.take() { - let window_id = wayland::make_wid(&dnd_offer.surface); - self.events_sink.push_window_event(WindowEvent::HoveredFileCancelled, window_id); + fn leave(&mut self, _: &Connection, _: &QueueHandle, wl_data_device: &WlDataDevice) { + let data_device = self.get_data_device(wl_data_device); + + if let Some(data_device) = data_device { + if let Some(dnd_offer) = self.dnd_offers.remove(&data_device.inner().id()) { + let window_id = wayland::make_wid(&dnd_offer.surface); + self.events_sink.push_window_event(WindowEvent::HoveredFileCancelled, window_id); + } } } @@ -84,7 +89,7 @@ impl DataDeviceHandler for WinitState { if let Some(offer) = data_device.data().drag_offer() { let window_id = wayland::make_wid(&offer.surface); - if let Some(dnd_offer) = self.dnd_offer.as_ref() { + if let Some(dnd_offer) = self.dnd_offers.get(&data_device.inner().id()) { self.events_sink.push_window_event( WindowEvent::HoveredFile(dnd_offer.path.to_path_buf()), window_id, @@ -108,7 +113,7 @@ impl DataDeviceHandler for WinitState { if let Some(offer) = data_device.data().drag_offer() { let window_id = wayland::make_wid(&offer.surface); - if let Some(dnd_offer) = self.dnd_offer.take() { + if let Some(dnd_offer) = self.dnd_offers.remove(&data_device.inner().id()) { self.events_sink .push_window_event(WindowEvent::DroppedFile(dnd_offer.path), window_id); diff --git a/src/platform_impl/linux/wayland/state.rs b/src/platform_impl/linux/wayland/state.rs index febad89ca5..2b01fcfe2e 100644 --- a/src/platform_impl/linux/wayland/state.rs +++ b/src/platform_impl/linux/wayland/state.rs @@ -57,7 +57,7 @@ pub struct WinitState { pub seat_state: SeatState, pub data_device_manager_state: DataDeviceManagerState, - pub dnd_offer: Option, + pub dnd_offers: AHashMap, /// The shm for software buffers, such as cursors. pub shm: Shm, @@ -174,7 +174,7 @@ impl WinitState { seat_state, data_device_manager_state, - dnd_offer: None, + dnd_offers: AHashMap::default(), shm, custom_cursor_pool, From dbb55e126896887283c269c4460d0d7f80349012 Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 5 Jun 2025 00:38:11 +0200 Subject: [PATCH 5/5] docs(wayland): add comments for each fields added --- src/platform_impl/linux/wayland/seat/mod.rs | 1 + src/platform_impl/linux/wayland/state.rs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/platform_impl/linux/wayland/seat/mod.rs b/src/platform_impl/linux/wayland/seat/mod.rs index 24ab85af9c..9a45814293 100644 --- a/src/platform_impl/linux/wayland/seat/mod.rs +++ b/src/platform_impl/linux/wayland/seat/mod.rs @@ -67,6 +67,7 @@ pub struct WinitSeatState { /// Whether we have pending modifiers. modifiers_pending: bool, + /// The current data device. data_device: Option, } diff --git a/src/platform_impl/linux/wayland/state.rs b/src/platform_impl/linux/wayland/state.rs index 2b01fcfe2e..c292fe6e23 100644 --- a/src/platform_impl/linux/wayland/state.rs +++ b/src/platform_impl/linux/wayland/state.rs @@ -56,7 +56,10 @@ pub struct WinitState { /// The seat state responsible for all sorts of input. pub seat_state: SeatState, + // The state of the data device manager. pub data_device_manager_state: DataDeviceManagerState, + + // The active drag and drop offers. pub dnd_offers: AHashMap, /// The shm for software buffers, such as cursors.