server: verify selection offer is still valid when handling clipboard

Should fix #170, fix #183
This commit is contained in:
Shawn Wallace 2025-06-29 01:37:24 -04:00
parent 2e7c318ac2
commit cf1fae1eae
4 changed files with 49 additions and 14 deletions

View file

@ -3,7 +3,8 @@ use hecs::{Entity, World};
use smithay_client_toolkit::{ use smithay_client_toolkit::{
activation::{ActivationHandler, RequestData, RequestDataExt}, activation::{ActivationHandler, RequestData, RequestDataExt},
data_device_manager::{ data_device_manager::{
data_device::DataDeviceHandler, data_offer::DataOfferHandler, data_device::{DataDeviceData, DataDeviceHandler},
data_offer::{DataOfferHandler, SelectionOffer},
data_source::DataSourceHandler, data_source::DataSourceHandler,
}, },
delegate_activation, delegate_data_device, delegate_activation, delegate_data_device,
@ -78,7 +79,7 @@ pub(super) struct MyWorld {
pub new_globals: Vec<Global>, pub new_globals: Vec<Global>,
events: Vec<(Entity, ObjectEvent)>, events: Vec<(Entity, ObjectEvent)>,
queued_events: Vec<mpsc::Receiver<(Entity, ObjectEvent)>>, queued_events: Vec<mpsc::Receiver<(Entity, ObjectEvent)>>,
pub selection: Option<wayland_client::protocol::wl_data_device::WlDataDevice>, pub selection_offer: Option<SelectionOffer>,
pub selection_requests: Vec<( pub selection_requests: Vec<(
String, String,
smithay_client_toolkit::data_device_manager::WritePipe, smithay_client_toolkit::data_device_manager::WritePipe,
@ -95,7 +96,7 @@ impl MyWorld {
new_globals: Vec::new(), new_globals: Vec::new(),
events: Vec::new(), events: Vec::new(),
queued_events: Vec::new(), queued_events: Vec::new(),
selection: None, selection_offer: None,
selection_requests: Vec::new(), selection_requests: Vec::new(),
selection_cancelled: false, selection_cancelled: false,
pending_activations: Vec::new(), pending_activations: Vec::new(),
@ -385,7 +386,8 @@ impl DataDeviceHandler for MyWorld {
_: &wayland_client::QueueHandle<Self>, _: &wayland_client::QueueHandle<Self>,
data_device: &wayland_client::protocol::wl_data_device::WlDataDevice, 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( fn drop_performed(

View file

@ -1095,16 +1095,18 @@ impl<C: XConnection> ServerState<C> {
} }
} }
if clipboard.source.is_none() || self.world.selection_cancelled { if clipboard.source.is_none() {
if self.world.selection.take().is_some() { if let Some(offer) = self.world.selection_offer.take() {
let device = clipboard.device.as_ref().unwrap(); if offer.inner().is_alive() {
let offer = device.data().selection_offer().unwrap(); let mime_types: Box<[String]> = offer.with_mime_types(|mimes| mimes.into());
let mime_types: Box<[String]> = offer.with_mime_types(|mimes| mimes.into()); let foreign = ForeignSelection {
let foreign = ForeignSelection { mime_types,
mime_types, inner: offer,
inner: offer, };
}; clipboard.source = Some(CopyPasteData::Foreign(foreign));
clipboard.source = Some(CopyPasteData::Foreign(foreign)); } else {
clipboard.source = None;
}
} }
self.world.selection_cancelled = false; self.world.selection_cancelled = false;
} }

View file

@ -2427,6 +2427,23 @@ fn output_updated_before_x_connection() {
assert_eq!(data.dims.x, 0); assert_eq!(data.dims.x, 0);
assert_eq!(data.dims.y, 0); assert_eq!(data.dims.y, 0);
} }
#[test]
fn quick_empty_data_offer() {
let (mut f, comp) = TestFixture::new_with_compositor();
TestObject::<WlKeyboard>::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. /// See Pointer::handle_event for an explanation.
#[test] #[test]
fn popup_pointer_motion_workaround() {} fn popup_pointer_motion_workaround() {}

View file

@ -695,6 +695,20 @@ impl Server {
self.display.flush_clients().unwrap(); 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] #[track_caller]
pub fn move_pointer_to(&mut self, surface: SurfaceId, x: f64, y: f64) { pub fn move_pointer_to(&mut self, surface: SurfaceId, x: f64, y: f64) {
let pointer = self.state.pointer.as_ref().expect("No pointer created"); let pointer = self.state.pointer.as_ref().expect("No pointer created");