From cf1fae1eae3b4d661e121232708a17afe173d790 Mon Sep 17 00:00:00 2001 From: Shawn Wallace Date: Sun, 29 Jun 2025 01:37:24 -0400 Subject: [PATCH] server: verify selection offer is still valid when handling clipboard Should fix #170, fix #183 --- src/server/clientside.rs | 10 ++++++---- src/server/mod.rs | 22 ++++++++++++---------- src/server/tests.rs | 17 +++++++++++++++++ testwl/src/lib.rs | 14 ++++++++++++++ 4 files changed, 49 insertions(+), 14 deletions(-) diff --git a/src/server/clientside.rs b/src/server/clientside.rs index 79ba099..0f910d0 100644 --- a/src/server/clientside.rs +++ b/src/server/clientside.rs @@ -3,7 +3,8 @@ use hecs::{Entity, World}; use smithay_client_toolkit::{ activation::{ActivationHandler, RequestData, RequestDataExt}, data_device_manager::{ - data_device::DataDeviceHandler, data_offer::DataOfferHandler, + data_device::{DataDeviceData, DataDeviceHandler}, + data_offer::{DataOfferHandler, SelectionOffer}, data_source::DataSourceHandler, }, delegate_activation, delegate_data_device, @@ -78,7 +79,7 @@ pub(super) struct MyWorld { pub new_globals: Vec, events: Vec<(Entity, ObjectEvent)>, queued_events: Vec>, - pub selection: Option, + pub selection_offer: Option, pub selection_requests: Vec<( String, smithay_client_toolkit::data_device_manager::WritePipe, @@ -95,7 +96,7 @@ impl MyWorld { new_globals: Vec::new(), events: Vec::new(), queued_events: Vec::new(), - selection: None, + selection_offer: None, selection_requests: Vec::new(), selection_cancelled: false, pending_activations: Vec::new(), @@ -385,7 +386,8 @@ impl DataDeviceHandler for MyWorld { _: &wayland_client::QueueHandle, data_device: &wayland_client::protocol::wl_data_device::WlDataDevice, ) { - self.selection = Some(data_device.clone()); + let data: &DataDeviceData = data_device.data().unwrap(); + self.selection_offer = data.selection_offer(); } fn drop_performed( diff --git a/src/server/mod.rs b/src/server/mod.rs index 4680ab5..6810fd3 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1095,16 +1095,18 @@ impl ServerState { } } - if clipboard.source.is_none() || self.world.selection_cancelled { - if self.world.selection.take().is_some() { - let device = clipboard.device.as_ref().unwrap(); - let offer = device.data().selection_offer().unwrap(); - let mime_types: Box<[String]> = offer.with_mime_types(|mimes| mimes.into()); - let foreign = ForeignSelection { - mime_types, - inner: offer, - }; - clipboard.source = Some(CopyPasteData::Foreign(foreign)); + if clipboard.source.is_none() { + if let Some(offer) = self.world.selection_offer.take() { + if offer.inner().is_alive() { + let mime_types: Box<[String]> = offer.with_mime_types(|mimes| mimes.into()); + let foreign = ForeignSelection { + mime_types, + inner: offer, + }; + clipboard.source = Some(CopyPasteData::Foreign(foreign)); + } else { + clipboard.source = None; + } } self.world.selection_cancelled = false; } diff --git a/src/server/tests.rs b/src/server/tests.rs index 0bdde9b..786dfc2 100644 --- a/src/server/tests.rs +++ b/src/server/tests.rs @@ -2427,6 +2427,23 @@ fn output_updated_before_x_connection() { assert_eq!(data.dims.x, 0); assert_eq!(data.dims.y, 0); } + +#[test] +fn quick_empty_data_offer() { + let (mut f, comp) = TestFixture::new_with_compositor(); + TestObject::::from_request(&comp.seat.obj, wl_seat::Request::GetKeyboard {}); + let win = unsafe { Window::new(1) }; + let (_surface, _id) = f.create_toplevel(&comp, win); + f.testwl.create_data_offer(vec![testwl::PasteData { + mime_type: "text".to_string(), + data: b"abc".to_vec(), + }]); + f.testwl.empty_data_offer(); + f.run(); + + let selection = f.satellite.new_selection(); + assert!(selection.is_none()); +} /// See Pointer::handle_event for an explanation. #[test] fn popup_pointer_motion_workaround() {} diff --git a/testwl/src/lib.rs b/testwl/src/lib.rs index d00f279..06197f1 100644 --- a/testwl/src/lib.rs +++ b/testwl/src/lib.rs @@ -695,6 +695,20 @@ impl Server { self.display.flush_clients().unwrap(); } + #[track_caller] + pub fn empty_data_offer(&mut self) { + let Some(dev) = &self.state.data_device else { + panic!("No data device created"); + }; + + if let Some(selection) = self.state.selection.take() { + selection.cancelled(); + } + + dev.selection(None); + self.display.flush_clients().unwrap(); + } + #[track_caller] pub fn move_pointer_to(&mut self, surface: SurfaceId, x: f64, y: f64) { let pointer = self.state.pointer.as_ref().expect("No pointer created");