From efe7a75e7b3f3850c4d1f7243168c6d962b241d8 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 26 Mar 2024 17:13:38 -0400 Subject: [PATCH 01/10] refactor: Allow requesting a specific mime type when peeking at the offer --- examples/clipboard.rs | 11 ++++++----- src/dnd/mod.rs | 8 ++++++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/examples/clipboard.rs b/examples/clipboard.rs index d916e4f..62ca53e 100644 --- a/examples/clipboard.rs +++ b/examples/clipboard.rs @@ -88,9 +88,9 @@ fn main() { smithay_clipboard::dnd::DndEvent::Offer(id, OfferEvent::Motion { x, y }) => { if id != state.offer_hover_id { state.offer_hover_id = id; - if let Ok(data) = state.clipboard.peek_offer::( - MimeType::Text(smithay_clipboard::mime::Text::TextPlain), - ) { + if let Ok(data) = + state.clipboard.peek_offer::(None) + { println!("Peeked the data: {}", data.0); } } @@ -117,8 +117,9 @@ fn main() { println!("Received DnD Enter for {id:?}"); state.offer_hover_id = id; if let Some(mime) = mime_types.get(0) { - if let Ok(data) = - state.clipboard.peek_offer::(mime.clone()) + if let Ok(data) = state + .clipboard + .peek_offer::(Some(mime.clone())) { println!("Peeked the data: {}", data.0); } diff --git a/src/dnd/mod.rs b/src/dnd/mod.rs index 06706e4..2862284 100644 --- a/src/dnd/mod.rs +++ b/src/dnd/mod.rs @@ -203,7 +203,7 @@ impl Clipboard { /// Register a surface for receiving DnD offers /// Rectangles should be provided in order of decreasing priority. - /// This method can be called multiple time for a single surface if the + /// This method c~an be called multiple time for a single surface if the /// rectangles change. pub fn register_dnd_destination(&self, surface: T, rectangles: Vec) { let s = DndSurface::new(surface, &self.connection).unwrap(); @@ -223,8 +223,12 @@ impl Clipboard { /// Peek at the contents of a DnD offer pub fn peek_offer( &self, - mime_type: MimeType, + mime_type: Option, ) -> std::io::Result { + let Some(mime_type) = mime_type.or_else(|| D::allowed().first().cloned()) else { + return Err(std::io::Error::new(std::io::ErrorKind::Other, "No mime type provided.")); + }; + self.request_sender .send(crate::worker::Command::DndRequest(DndRequest::Peek(mime_type))) .map_err(|_| { From ace619fd01e2de32e50b6ad276e9f1c080e59718 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 26 Mar 2024 23:22:47 -0400 Subject: [PATCH 02/10] refactor: add buffer variant to icon argument when starting DnD --- Cargo.toml | 20 +++++++--- examples/clipboard.rs | 18 +++++++-- src/dnd/mod.rs | 22 ++++++++-- src/dnd/state.rs | 53 +++++++++++++++++++----- src/state.rs | 93 ++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 184 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4b208ea..72d4a83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "smithay-clipboard" version = "0.8.0" -authors = ["Kirill Chibisov ", "Victor Berger "] +authors = [ + "Kirill Chibisov ", + "Victor Berger ", +] edition = "2021" description = "Provides access to the wayland clipboard for client applications." repository = "https://github.com/smithay/smithay-clipboard" @@ -12,16 +15,23 @@ rust-version = "1.65.0" [dependencies] libc = "0.2.149" -sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", default-features = false, features = ["calloop"] } -wayland-backend = { version = "0.3.3", default_features = false, features = ["client_system"] } +sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", default-features = false, features = [ + "calloop", +] } +wayland-backend = { version = "0.3.3", default_features = false, features = [ + "client_system", +] } [dev-dependencies] dirs = "5.0.1" -sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", default-features = false, features = ["calloop", "xkbcommon"] } +sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", default-features = false, features = [ + "calloop", + "xkbcommon", +] } thiserror = "1.0.57" url = "2.5.0" [features] default = ["dlopen", "dnd"] dnd = [] -dlopen = ["wayland-backend/dlopen" ] +dlopen = ["wayland-backend/dlopen"] diff --git a/examples/clipboard.rs b/examples/clipboard.rs index 62ca53e..ead979c 100644 --- a/examples/clipboard.rs +++ b/examples/clipboard.rs @@ -30,7 +30,7 @@ use sctk::{ delegate_compositor, delegate_keyboard, delegate_output, delegate_pointer, delegate_registry, delegate_seat, delegate_shm, delegate_xdg_shell, delegate_xdg_window, registry_handlers, }; -use smithay_clipboard::dnd::{DndDestinationRectangle, OfferEvent, Rectangle, SourceEvent}; +use smithay_clipboard::dnd::{DndDestinationRectangle, Icon, OfferEvent, Rectangle, SourceEvent}; use smithay_clipboard::mime::{AllowedMimeTypes, AsMimeTypes, MimeType, ALLOWED_TEXT_MIME_TYPES}; use smithay_clipboard::{Clipboard, SimpleClipboard}; use thiserror::Error; @@ -391,21 +391,33 @@ impl PointerHandler for SimpleWindow { match &e.kind { PointerEventKind::Press { button, .. } if *button == BTN_LEFT => { println!("Starting a drag!"); + self.clipboard.start_dnd( false, self.window.wl_surface().clone(), - None, + Some(Icon::Buf { + width: 256, + height: 256, + data: vec![0x99; 256 * 256 * 4], + transparent: true, + }), smithay_clipboard::text::Text("Clipboard Drag and Drop!".to_string()), DndAction::all(), ); }, PointerEventKind::Press { button, .. } if *button == BTN_RIGHT => { println!("Starting an internal drag!"); + self.internal_dnd = true; self.clipboard.start_dnd( true, self.window.wl_surface().clone(), - None, + Some(Icon::Buf { + width: 256, + height: 256, + data: vec![0xFF; 256 * 256 * 4], + transparent: true, + }), smithay_clipboard::text::Text( "Internal clipboard Drag and Drop!".to_string(), ), diff --git a/src/dnd/mod.rs b/src/dnd/mod.rs index 2862284..f3d1e68 100644 --- a/src/dnd/mod.rs +++ b/src/dnd/mod.rs @@ -135,7 +135,7 @@ pub enum DndRequest { StartDnd { internal: bool, source: DndSurface, - icon: Option>, + icon: Option>>, content: Box, actions: DndAction, }, @@ -167,6 +167,17 @@ impl Sender for calloop::channel::SyncSender> { } } +pub enum Icon { + Surface(S), + /// Argb8888 or Xrgb8888 encoded image data pre-multiplied by alpha. + Buf { + width: u32, + height: u32, + data: Vec, + transparent: bool, + }, +} + impl Clipboard { /// Set up DnD operations for the Clipboard pub fn init_dnd( @@ -181,12 +192,17 @@ impl Clipboard { &self, internal: bool, source_surface: T, - icon_surface: Option, + icon_surface: Option>, content: D, actions: DndAction, ) { let source = DndSurface::new(source_surface, &self.connection).unwrap(); - let icon = icon_surface.map(|s| DndSurface::new(s, &self.connection).unwrap()); + let icon = icon_surface.map(|i| match i { + Icon::Surface(s) => Icon::Surface(DndSurface::new(s, &self.connection).unwrap()), + Icon::Buf { width, height, data, transparent } => { + Icon::Buf { width, height, data, transparent } + }, + }); _ = self.request_sender.send(crate::worker::Command::DndRequest(DndRequest::StartDnd { internal, source, diff --git a/src/dnd/state.rs b/src/dnd/state.rs index 1bf6ca5..514d8ff 100644 --- a/src/dnd/state.rs +++ b/src/dnd/state.rs @@ -9,6 +9,7 @@ use sctk::data_device_manager::WritePipe; use sctk::reexports::calloop::PostAction; 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_shm::Format; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::Proxy; use wayland_backend::client::ObjectId; @@ -16,7 +17,7 @@ use wayland_backend::client::ObjectId; use crate::mime::{AsMimeTypes, MimeType}; use crate::state::{set_non_blocking, State}; -use super::{DndDestinationRectangle, DndEvent, DndRequest, DndSurface, OfferEvent}; +use super::{DndDestinationRectangle, DndEvent, DndRequest, DndSurface, Icon, OfferEvent}; pub(crate) struct DndState { pub(crate) sender: Option>>, @@ -26,6 +27,7 @@ pub(crate) struct DndState { source_actions: DndAction, selected_action: DndAction, selected_mime: Option, + pub(crate) icon_surface: Option, pub(crate) source_content: Option>, accept_ctr: u32, } @@ -42,6 +44,7 @@ impl Default for DndState { selected_mime: None, source_content: None, accept_ctr: 1, + icon_surface: None, } } } @@ -262,6 +265,8 @@ where DndRequest::DndEnd => { self.dnd_state.source_content = None; self.dnd_state.dnd_source = None; + self.pool.remove(&0); + self.dnd_state.icon_surface = None; }, DndRequest::Peek(mime_type) => { if let Err(err) = self.load_dnd(mime_type, true) { @@ -275,7 +280,7 @@ where &mut self, internal: bool, source_surface: DndSurface, - icon: Option>, + mut icon: Option>>, content: Box, actions: DndAction, ) -> std::io::Result<()> { @@ -294,11 +299,36 @@ where .as_ref() .ok_or_else(|| Error::new(ErrorKind::Other, "data device missing"))?; + let (icon_surface, buffer) = if let Some(i) = icon.take() { + match i { + Icon::Surface(s) => (Some(s.surface.clone()), None), + Icon::Buf { data, width, height, transparent } => { + let surface = self.compositor_state.create_surface(&self.queue_handle); + self.pool.remove(&0); + let (_, wl_buffer, buf) = self + .pool + .create_buffer( + width as i32, + width as i32 * 4, + height as i32, + &0, + if transparent { Format::Argb8888 } else { Format::Xrgb8888 }, + ) + .map_err(|err| Error::new(ErrorKind::Other, err))?; + buf.copy_from_slice(&data); + + (Some(surface), Some((wl_buffer, width, height))) + }, + } + } else { + (None, None) + }; + if internal { DragSource::start_internal_drag( data_device, &source_surface.surface, - icon.as_ref().map(|s| &s.surface), + icon_surface.as_ref(), serial, ) } else { @@ -314,17 +344,22 @@ where ) }) .ok_or_else(|| Error::new(ErrorKind::Other, "data device manager missing"))?; - source.start_drag( - data_device, - &source_surface.surface, - icon.as_ref().map(|s| &s.surface), - serial, - ); + source.start_drag(data_device, &source_surface.surface, icon_surface.as_ref(), serial); + self.dnd_state.dnd_source = Some(source); self.dnd_state.source_content = Some(content); self.dnd_state.source_actions = actions; } + if let (Some((wl_buffer, width, height)), Some(surface)) = (buffer, icon_surface) { + surface.damage_buffer(0, 0, width as i32, height as i32); + surface.attach(Some(&wl_buffer), 0, 0); + surface.commit(); + + dbg!("attached buffer, damaged surface."); + self.dnd_state.icon_surface = Some(surface); + } + Ok(()) } diff --git a/src/state.rs b/src/state.rs index 259c4f2..68eded7 100644 --- a/src/state.rs +++ b/src/state.rs @@ -7,10 +7,12 @@ use std::os::unix::io::{AsRawFd, RawFd}; use std::rc::Rc; use std::sync::mpsc::Sender; +use sctk::compositor::{CompositorHandler, CompositorState}; use sctk::data_device_manager::data_device::{DataDevice, DataDeviceHandler}; use sctk::data_device_manager::data_offer::{DataOfferError, DataOfferHandler, DragOffer}; use sctk::data_device_manager::data_source::{CopyPasteSource, DataSourceHandler}; use sctk::data_device_manager::{DataDeviceManagerState, WritePipe}; +use sctk::output::{OutputHandler, OutputState}; use sctk::primary_selection::device::{PrimarySelectionDevice, PrimarySelectionDeviceHandler}; use sctk::primary_selection::selection::{PrimarySelectionSource, PrimarySelectionSourceHandler}; use sctk::primary_selection::PrimarySelectionManagerState; @@ -18,9 +20,11 @@ use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::registry::{ProvidesRegistryState, RegistryState}; use sctk::seat::pointer::{PointerData, PointerEvent, PointerEventKind, PointerHandler}; use sctk::seat::{Capability, SeatHandler, SeatState}; +use sctk::shm::multi::MultiPool; +use sctk::shm::{Shm, ShmHandler}; use sctk::{ - delegate_data_device, delegate_pointer, delegate_primary_selection, delegate_registry, - delegate_seat, registry_handlers, + delegate_compositor, delegate_data_device, delegate_output, delegate_pointer, + delegate_primary_selection, delegate_registry, delegate_seat, delegate_shm, registry_handlers, }; use sctk::reexports::calloop::{LoopHandle, PostAction}; @@ -68,6 +72,10 @@ pub struct State { data_selection_mime_types: Rc>, #[cfg(feature = "dnd")] pub(crate) dnd_state: crate::dnd::state::DndState, + pub(crate) compositor_state: CompositorState, + output_state: OutputState, + pub(crate) shm: Shm, + pub(crate) pool: MultiPool, _phantom: PhantomData, } @@ -90,6 +98,11 @@ impl State { return None; } + let compositor_state = + CompositorState::bind(&globals, &queue_handle).expect("wl_compositor not available"); + let output_state = OutputState::new(&globals, &queue_handle); + let shm = Shm::bind(&globals, &queue_handle).expect("wl_shm not available"); + let seat_state = SeatState::new(globals, queue_handle); for seat in seat_state.seats() { seats.insert(seat.id(), Default::default()); @@ -115,6 +128,10 @@ impl State { #[cfg(feature = "dnd")] dnd_state: DndState::default(), _phantom: PhantomData, + compositor_state, + output_state, + pool: MultiPool::new(&shm).expect("Failed to create memory pool."), + shm, }) } @@ -475,6 +492,8 @@ impl DataSourceHandler for State { if let Some(s) = self.dnd_state.sender.as_ref() { _ = s.send(DndEvent::Source(crate::dnd::SourceEvent::Cancelled)); } + _ = self.pool.remove(&0); + self.dnd_state.icon_surface = None; } } @@ -501,6 +520,8 @@ impl DataSourceHandler for State { if let Some(s) = self.dnd_state.sender.as_ref() { _ = s.send(DndEvent::Source(crate::dnd::SourceEvent::Dropped)) } + _ = self.pool.remove(&0); + self.dnd_state.icon_surface = None; } } @@ -620,6 +641,74 @@ impl Dispatch> for State { } } +impl CompositorHandler for State { + fn scale_factor_changed( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _surface: &sctk::reexports::client::protocol::wl_surface::WlSurface, + _new_factor: i32, + ) { + } + + fn transform_changed( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _surface: &sctk::reexports::client::protocol::wl_surface::WlSurface, + _new_transform: sctk::reexports::client::protocol::wl_output::Transform, + ) { + } + + fn frame( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _surface: &sctk::reexports::client::protocol::wl_surface::WlSurface, + _time: u32, + ) { + } +} + +impl OutputHandler for State { + fn output_state(&mut self) -> &mut sctk::output::OutputState { + &mut self.output_state + } + + fn new_output( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _output: sctk::reexports::client::protocol::wl_output::WlOutput, + ) { + } + + fn update_output( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _output: sctk::reexports::client::protocol::wl_output::WlOutput, + ) { + } + + fn output_destroyed( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _output: sctk::reexports::client::protocol::wl_output::WlOutput, + ) { + } +} + +impl ShmHandler for State { + fn shm_state(&mut self) -> &mut Shm { + &mut self.shm + } +} + +delegate_compositor!(@ State); +delegate_output!(@ State); +delegate_shm!(@ State); delegate_seat!(@ State); delegate_pointer!(@ State); delegate_data_device!(@ State); From a698efe935a2c2c692ee02e97e5d5a2d8dd6139e Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 27 Mar 2024 00:53:02 -0400 Subject: [PATCH 03/10] cleanup: fmt + clippy --- examples/clipboard.rs | 4 ++-- src/dnd/state.rs | 2 +- src/state.rs | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/clipboard.rs b/examples/clipboard.rs index ead979c..b2fe60f 100644 --- a/examples/clipboard.rs +++ b/examples/clipboard.rs @@ -116,7 +116,7 @@ fn main() { smithay_clipboard::dnd::DndEvent::Offer(id, OfferEvent::Enter { mime_types, .. }) => { println!("Received DnD Enter for {id:?}"); state.offer_hover_id = id; - if let Some(mime) = mime_types.get(0) { + if let Some(mime) = mime_types.first() { if let Ok(data) = state .clipboard .peek_offer::(Some(mime.clone())) @@ -589,7 +589,7 @@ impl SimpleWindow { }; // Draw to the window: - canvas.chunks_exact_mut(4).enumerate().for_each(|(_, chunk)| { + canvas.chunks_exact_mut(4).for_each(|chunk| { // ARGB color. let color = 0xFF181818u32; diff --git a/src/dnd/state.rs b/src/dnd/state.rs index 514d8ff..391790b 100644 --- a/src/dnd/state.rs +++ b/src/dnd/state.rs @@ -353,7 +353,7 @@ where if let (Some((wl_buffer, width, height)), Some(surface)) = (buffer, icon_surface) { surface.damage_buffer(0, 0, width as i32, height as i32); - surface.attach(Some(&wl_buffer), 0, 0); + surface.attach(Some(wl_buffer), 0, 0); surface.commit(); dbg!("attached buffer, damaged surface."); diff --git a/src/state.rs b/src/state.rs index 68eded7..65f1a43 100644 --- a/src/state.rs +++ b/src/state.rs @@ -99,9 +99,9 @@ impl State { } let compositor_state = - CompositorState::bind(&globals, &queue_handle).expect("wl_compositor not available"); - let output_state = OutputState::new(&globals, &queue_handle); - let shm = Shm::bind(&globals, &queue_handle).expect("wl_shm not available"); + CompositorState::bind(globals, queue_handle).expect("wl_compositor not available"); + let output_state = OutputState::new(globals, queue_handle); + let shm = Shm::bind(globals, queue_handle).expect("wl_shm not available"); let seat_state = SeatState::new(globals, queue_handle); for seat in seat_state.seats() { From 2263426a09f47fb2158a07675dd1f37c1d4759c3 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 27 Mar 2024 23:46:24 -0400 Subject: [PATCH 04/10] chore: add rwh-6 feature --- Cargo.toml | 4 +++- src/dnd/mod.rs | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 72d4a83..259f97f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/c wayland-backend = { version = "0.3.3", default_features = false, features = [ "client_system", ] } +raw-window-handle = { version = "0.6", optional = true } [dev-dependencies] dirs = "5.0.1" @@ -32,6 +33,7 @@ thiserror = "1.0.57" url = "2.5.0" [features] -default = ["dlopen", "dnd"] +default = ["dlopen", "dnd", "rwh-6"] +rwh-6 = ["raw-window-handle"] dnd = [] dlopen = ["wayland-backend/dlopen"] diff --git a/src/dnd/mod.rs b/src/dnd/mod.rs index f3d1e68..f3bad40 100644 --- a/src/dnd/mod.rs +++ b/src/dnd/mod.rs @@ -34,6 +34,16 @@ impl DndSurface { } } +#[cfg(feature = "rwh-6")] +impl<'a> RawSurface for raw_window_handle::WindowHandle<'a> { + unsafe fn get_ptr(&mut self) -> *mut c_void { + match self.as_raw() { + raw_window_handle::RawWindowHandle::Wayland(handle) => handle.surface.as_ptr().cast(), + _ => panic!("Unsupported window handle type."), + } + } +} + impl RawSurface for WlSurface { unsafe fn get_ptr(&mut self) -> *mut c_void { self.id().as_ptr().cast() From eefa50c3df5135d98df7f4192e2e9b07eeafe56b Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 1 May 2024 11:52:12 -0400 Subject: [PATCH 05/10] fix: remove a destination surface if it is assigned empty list of rectangles --- src/dnd/state.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/dnd/state.rs b/src/dnd/state.rs index 391790b..8d87ff9 100644 --- a/src/dnd/state.rs +++ b/src/dnd/state.rs @@ -254,7 +254,11 @@ where match r { DndRequest::InitDnd(sender) => self.dnd_state.sender = Some(sender), DndRequest::Surface(s, dests) => { - self.dnd_state.destinations.insert(s.surface.id(), (s, dests)); + if dests.is_empty() { + self.dnd_state.destinations.remove(&s.surface.id()); + } else { + self.dnd_state.destinations.insert(s.surface.id(), (s, dests)); + } }, DndRequest::StartDnd { internal, source, icon, content, actions } => { _ = self.start_dnd(internal, source, icon, content, actions); From 9b995a33a88c496a90259dd207367a998c95ac98 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 3 May 2024 11:47:45 -0400 Subject: [PATCH 06/10] fix: add rev for sctk --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 259f97f..2065a05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ rust-version = "1.65.0" libc = "0.2.149" sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", default-features = false, features = [ "calloop", -] } +], rev = "3bed072" } wayland-backend = { version = "0.3.3", default_features = false, features = [ "client_system", ] } @@ -28,7 +28,7 @@ dirs = "5.0.1" sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", default-features = false, features = [ "calloop", "xkbcommon", -] } +], rev = "3bed072" } thiserror = "1.0.57" url = "2.5.0" From ab1750a028b486548b9c1f57820c21564a8bd0ac Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 11 Jun 2024 13:46:42 -0700 Subject: [PATCH 07/10] Update sctk to `0.19.1` --- Cargo.toml | 8 ++++---- src/state.rs | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2065a05..6e0ff6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,9 +15,9 @@ rust-version = "1.65.0" [dependencies] libc = "0.2.149" -sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", default-features = false, features = [ +sctk = { package = "smithay-client-toolkit", version = "0.19.1", default-features = false, features = [ "calloop", -], rev = "3bed072" } +] } wayland-backend = { version = "0.3.3", default_features = false, features = [ "client_system", ] } @@ -25,10 +25,10 @@ raw-window-handle = { version = "0.6", optional = true } [dev-dependencies] dirs = "5.0.1" -sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", default-features = false, features = [ +sctk = { package = "smithay-client-toolkit", version = "0.19.1", default-features = false, features = [ "calloop", "xkbcommon", -], rev = "3bed072" } +] } thiserror = "1.0.57" url = "2.5.0" diff --git a/src/state.rs b/src/state.rs index 65f1a43..afa2adb 100644 --- a/src/state.rs +++ b/src/state.rs @@ -16,6 +16,7 @@ use sctk::output::{OutputHandler, OutputState}; use sctk::primary_selection::device::{PrimarySelectionDevice, PrimarySelectionDeviceHandler}; use sctk::primary_selection::selection::{PrimarySelectionSource, PrimarySelectionSourceHandler}; use sctk::primary_selection::PrimarySelectionManagerState; +use sctk::reexports::client::protocol::wl_output::WlOutput; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::registry::{ProvidesRegistryState, RegistryState}; use sctk::seat::pointer::{PointerData, PointerEvent, PointerEventKind, PointerHandler}; @@ -668,6 +669,24 @@ impl CompositorHandler for State { _time: u32, ) { } + + fn surface_enter( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &WlSurface, + _: &WlOutput, + ) { + } + + fn surface_leave( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &WlSurface, + _: &WlOutput, + ) { + } } impl OutputHandler for State { From d099e82a4c1e7d3e88dc34b7333de21928b1b22c Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 4 Apr 2024 18:13:46 -0400 Subject: [PATCH 08/10] fix: send selected action event --- src/dnd/state.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/dnd/state.rs b/src/dnd/state.rs index 8d87ff9..3ef7b30 100644 --- a/src/dnd/state.rs +++ b/src/dnd/state.rs @@ -52,6 +52,12 @@ impl Default for DndState { impl DndState { pub(crate) fn selected_action(&mut self, a: DndAction) { self.selected_action = a; + if let Some(tx) = self.sender.as_ref() { + _ = tx.send(DndEvent::Offer( + self.active_surface.as_ref().and_then(|(_, d)| d.as_ref().map(|d| d.id)), + OfferEvent::SelectedAction(a), + )); + } } } @@ -267,10 +273,12 @@ where _ = self.user_selected_action(a); }, DndRequest::DndEnd => { + if let Some(s) = self.dnd_state.icon_surface.take() { + _ = s.destroy(); + } self.dnd_state.source_content = None; self.dnd_state.dnd_source = None; self.pool.remove(&0); - self.dnd_state.icon_surface = None; }, DndRequest::Peek(mime_type) => { if let Err(err) = self.load_dnd(mime_type, true) { From 5a3007def49eb678d1144850c9ee04b80707c56a Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 23 Jul 2024 19:56:01 -0400 Subject: [PATCH 09/10] fix: always recalculate DnD rectangle on offer motion in case it changes Sometimes DnD targets can be nested inside other containers which are also DnD targets. --- src/dnd/state.rs | 51 +++++++++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/src/dnd/state.rs b/src/dnd/state.rs index 3ef7b30..b4145e7 100644 --- a/src/dnd/state.rs +++ b/src/dnd/state.rs @@ -119,14 +119,37 @@ where } return (s.clone(), None); }; - if let (Some((action, preferred_action)), Some(mime_type), Some(dnd_state)) = - (actions, mime, dnd_state.as_ref()) - { - dnd_state.set_actions(action, preferred_action); - self.dnd_state.selected_mime = Some(mime_type.clone()); - dnd_state - .accept_mime_type(self.dnd_state.accept_ctr, Some(mime_type.to_string())); - self.dnd_state.accept_ctr = self.dnd_state.accept_ctr.wrapping_add(1); + if !had_dest.is_some_and(|old_id| old_id == dest.id) { + if let (Some((action, preferred_action)), Some(mime_type), Some(dnd_state)) = + (actions, mime, dnd_state.as_ref()) + { + if let Some((tx, old_id)) = self.dnd_state.sender.as_ref().zip(had_dest) { + _ = tx.send(DndEvent::Offer( + Some(old_id), + super::OfferEvent::LeaveDestination, + )); + } + if let Some(tx) = self.dnd_state.sender.as_ref() { + _ = tx.send(DndEvent::Offer(Some(dest.id), OfferEvent::Enter { + x, + y, + surface: s.s.clone(), + mime_types: dest.mime_types.clone(), + })); + + _ = tx.send(DndEvent::Offer( + Some(dest.id), + OfferEvent::SelectedAction(self.dnd_state.selected_action), + )); + } + dnd_state.set_actions(action, preferred_action); + self.dnd_state.selected_mime = Some(mime_type.clone()); + dnd_state.accept_mime_type( + self.dnd_state.accept_ctr, + Some(mime_type.to_string()), + ); + self.dnd_state.accept_ctr = self.dnd_state.accept_ctr.wrapping_add(1); + } } (s.clone(), Some(dest)) }); @@ -218,12 +241,7 @@ where } pub(crate) fn offer_motion(&mut self, x: f64, y: f64, wl_data_device: &WlDataDevice) { - let Some((surface, dest)) = self - .dnd_state - .active_surface - .clone() - .map(|(s, dest)| (s, dest.filter(|d| d.rectangle.contains(x, y)))) - else { + let Some(surface) = self.dnd_state.active_surface.clone().map(|(s, _)| s) else { return; }; let Some(data_device) = self @@ -238,9 +256,7 @@ where // Ignore cancelled internal DnD return; } - if dest.is_none() { - self.update_active_surface(&surface.surface, x, y, drag_offer.as_ref()); - } + self.update_active_surface(&surface.surface, x, y, drag_offer.as_ref()); let id = self.cur_id(); if let Some(tx) = self.dnd_state.sender.as_ref() { _ = tx.send(DndEvent::Offer(id, super::OfferEvent::Motion { x, y })); @@ -368,7 +384,6 @@ where surface.attach(Some(wl_buffer), 0, 0); surface.commit(); - dbg!("attached buffer, damaged surface."); self.dnd_state.icon_surface = Some(surface); } From 859b02c88f45c554049a67c6ddeec1692ce0e20b Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 2 Mar 2026 11:33:13 -0500 Subject: [PATCH 10/10] update sctk --- Cargo.toml | 4 ++-- examples/clipboard.rs | 37 +++++++++++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6e0ff6a..dbfd1f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ rust-version = "1.65.0" [dependencies] libc = "0.2.149" -sctk = { package = "smithay-client-toolkit", version = "0.19.1", default-features = false, features = [ +sctk = { package = "smithay-client-toolkit", version = "0.20", default-features = false, features = [ "calloop", ] } wayland-backend = { version = "0.3.3", default_features = false, features = [ @@ -25,7 +25,7 @@ raw-window-handle = { version = "0.6", optional = true } [dev-dependencies] dirs = "5.0.1" -sctk = { package = "smithay-client-toolkit", version = "0.19.1", default-features = false, features = [ +sctk = { package = "smithay-client-toolkit", version = "0.20", default-features = false, features = [ "calloop", "xkbcommon", ] } diff --git a/examples/clipboard.rs b/examples/clipboard.rs index b2fe60f..bbc98ce 100644 --- a/examples/clipboard.rs +++ b/examples/clipboard.rs @@ -265,6 +265,24 @@ impl CompositorHandler for SimpleWindow { ) { self.draw(conn, qh); } + + fn surface_enter( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _surface: &wl_surface::WlSurface, + _output: &wl_output::WlOutput, + ) { + } + + fn surface_leave( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _surface: &wl_surface::WlSurface, + _output: &wl_output::WlOutput, + ) { + } } impl OutputHandler for SimpleWindow { @@ -538,14 +556,25 @@ impl KeyboardHandler for SimpleWindow { ) { } + fn repeat_key( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _keyboard: &wl_keyboard::WlKeyboard, + _serial: u32, + _event: KeyEvent, + ) { + } + fn update_modifiers( &mut self, - _: &Connection, - _: &QueueHandle, - _: &wl_keyboard::WlKeyboard, + _conn: &Connection, + _qh: &QueueHandle, + _keyboard: &wl_keyboard::WlKeyboard, _serial: u32, _modifiers: Modifiers, - _: u32, + _raw_modifiers: sctk::seat::keyboard::RawModifiers, + _layout: u32, ) { } }