Support primary selection

This was more tedious than expected.
Fixes #103
This commit is contained in:
Shawn Wallace 2025-08-14 01:30:04 -04:00
parent 13469566b0
commit 5a184d4359
11 changed files with 1086 additions and 391 deletions

View file

@ -7,7 +7,12 @@ use smithay_client_toolkit::{
data_offer::{DataOfferHandler, SelectionOffer},
data_source::DataSourceHandler,
},
delegate_activation, delegate_data_device,
delegate_activation, delegate_data_device, delegate_primary_selection,
primary_selection::{
device::{PrimarySelectionDeviceData, PrimarySelectionDeviceHandler},
offer::PrimarySelectionOffer,
selection::PrimarySelectionSourceHandler,
},
};
use std::sync::{mpsc, Mutex, OnceLock};
use wayland_client::protocol::{
@ -41,6 +46,11 @@ use wayland_protocols::{
zwp_locked_pointer_v1::ZwpLockedPointerV1,
zwp_pointer_constraints_v1::ZwpPointerConstraintsV1,
},
primary_selection::zv1::client::{
zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1,
zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1,
zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1,
},
tablet::zv2::client::{
zwp_tablet_manager_v2::ZwpTabletManagerV2,
zwp_tablet_pad_group_v2::{ZwpTabletPadGroupV2, EVT_RING_OPCODE, EVT_STRIP_OPCODE},
@ -73,18 +83,33 @@ use wayland_server::protocol as server;
use wl_drm::client::wl_drm::WlDrm;
use xcb::x;
pub(super) struct SelectionEvents<T> {
pub offer: Option<T>,
pub requests: Vec<(
String,
smithay_client_toolkit::data_device_manager::WritePipe,
)>,
pub cancelled: bool,
}
impl<T> Default for SelectionEvents<T> {
fn default() -> Self {
Self {
offer: None,
requests: Default::default(),
cancelled: false,
}
}
}
pub(super) struct MyWorld {
pub world: World,
pub global_list: GlobalList,
pub new_globals: Vec<Global>,
events: Vec<(Entity, ObjectEvent)>,
queued_events: Vec<mpsc::Receiver<(Entity, ObjectEvent)>>,
pub selection_offer: Option<SelectionOffer>,
pub selection_requests: Vec<(
String,
smithay_client_toolkit::data_device_manager::WritePipe,
)>,
pub selection_cancelled: bool,
pub clipboard: SelectionEvents<SelectionOffer>,
pub primary: SelectionEvents<PrimarySelectionOffer>,
pub pending_activations: Vec<(xcb::x::Window, String)>,
}
@ -96,9 +121,8 @@ impl MyWorld {
new_globals: Vec::new(),
events: Vec::new(),
queued_events: Vec::new(),
selection_offer: None,
selection_requests: Vec::new(),
selection_cancelled: false,
clipboard: Default::default(),
primary: Default::default(),
pending_activations: Vec::new(),
}
}
@ -156,6 +180,7 @@ delegate_noop!(MyWorld: XdgActivationV1);
delegate_noop!(MyWorld: ZxdgDecorationManagerV1);
delegate_noop!(MyWorld: WpFractionalScaleManagerV1);
delegate_noop!(MyWorld: ignore ZxdgToplevelDecorationV1);
delegate_noop!(MyWorld: ZwpPrimarySelectionDeviceManagerV1);
impl Dispatch<WlRegistry, GlobalListContents> for MyWorld {
fn event(
@ -387,7 +412,7 @@ impl DataDeviceHandler for MyWorld {
data_device: &wayland_client::protocol::wl_data_device::WlDataDevice,
) {
let data: &DataDeviceData = data_device.data().unwrap();
self.selection_offer = data.selection_offer();
self.clipboard.offer = data.selection_offer();
}
fn drop_performed(
@ -437,7 +462,7 @@ impl DataSourceHandler for MyWorld {
mime: String,
fd: smithay_client_toolkit::data_device_manager::WritePipe,
) {
self.selection_requests.push((mime, fd));
self.clipboard.requests.push((mime, fd));
}
fn cancelled(
@ -446,7 +471,7 @@ impl DataSourceHandler for MyWorld {
_: &wayland_client::QueueHandle<Self>,
_: &wayland_client::protocol::wl_data_source::WlDataSource,
) {
self.selection_cancelled = true;
self.clipboard.cancelled = true;
}
fn action(
@ -538,3 +563,42 @@ impl ActivationHandler for MyWorld {
self.pending_activations.push((data.window, token));
}
}
delegate_primary_selection!(MyWorld);
impl PrimarySelectionDeviceHandler for MyWorld {
fn selection(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
primary_selection_device: &ZwpPrimarySelectionDeviceV1,
) {
let Some(data) = primary_selection_device.data::<PrimarySelectionDeviceData>() else {
return;
};
self.primary.offer = data.selection_offer();
}
}
impl PrimarySelectionSourceHandler for MyWorld {
fn send_request(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &ZwpPrimarySelectionSourceV1,
mime: String,
write_pipe: smithay_client_toolkit::data_device_manager::WritePipe,
) {
self.primary.requests.push((mime, write_pipe));
}
fn cancelled(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &ZwpPrimarySelectionSourceV1,
) {
self.primary.cancelled = true;
}
}

View file

@ -1341,9 +1341,8 @@ impl<S: X11Selection> GlobalDispatch<WlSeat, Global> for InnerServerState<S> {
.global_list
.registry()
.bind::<client::wl_seat::WlSeat, _, _>(data.name, server.version(), &state.qh, entity);
if let Some(c) = &mut state.clipboard_data {
c.device = Some(c.manager.get_data_device(&state.qh, &client));
}
state.selection_states.seat_created(&state.qh, &client);
state.world.spawn_at(entity, (server, client));
}
}

View file

@ -32,6 +32,7 @@ use wayland_protocols::{
zwp_tablet_v2::ZwpTabletV2 as TabletServer,
},
},
viewporter::client::wp_viewport::WpViewport,
},
xdg::{
shell::client::{xdg_popup, xdg_surface, xdg_toplevel},

View file

@ -1,6 +1,7 @@
mod clientside;
mod dispatch;
mod event;
pub(crate) mod selection;
#[cfg(test)]
mod tests;
@ -12,16 +13,10 @@ use hecs::{Entity, World};
use log::{debug, warn};
use rustix::event::{poll, PollFd, PollFlags};
use smithay_client_toolkit::activation::ActivationState;
use smithay_client_toolkit::data_device_manager::{
data_device::DataDevice, data_offer::SelectionOffer, data_source::CopyPasteSource,
DataDeviceManagerState,
};
use std::collections::{HashMap, HashSet};
use std::io::Read;
use std::ops::{Deref, DerefMut};
use std::os::fd::{AsFd, BorrowedFd};
use std::os::unix::net::UnixStream;
use std::rc::{Rc, Weak};
use wayland_client::{
globals::{registry_queue_init, Global},
protocol as client, Connection, EventQueue, Proxy, QueueHandle,
@ -47,7 +42,7 @@ use wayland_protocols::{
zwp_tablet_pad_v2, zwp_tablet_seat_v2, zwp_tablet_tool_v2, zwp_tablet_v2,
},
tablet::zv2::server::zwp_tablet_manager_v2::ZwpTabletManagerV2,
viewporter::client::{wp_viewport::WpViewport, wp_viewporter::WpViewporter},
viewporter::client::wp_viewporter::WpViewporter,
},
xdg::{
shell::client::{
@ -454,7 +449,7 @@ pub struct InnerServerState<S: X11Selection> {
viewporter: WpViewporter,
fractional_scale: Option<WpFractionalScaleManagerV1>,
decoration_manager: Option<ZxdgDecorationManagerV1>,
clipboard_data: Option<ClipboardData<S>>,
selection_states: selection::SelectionStates<S>,
last_kb_serial: Option<(client::wl_seat::WlSeat, u32)>,
activation_state: Option<ActivationState>,
global_output_offset: GlobalOutputOffset,
@ -494,17 +489,6 @@ impl<S: X11Selection> ServerState<NoConnection<S>> {
.inspect_err(|e| warn!("Couldn't bind fractional scale manager: {e}. Fractional scaling will not work."))
.ok();
let manager = DataDeviceManagerState::bind(&global_list, &qh)
.inspect_err(|e| {
warn!("Could not bind data device manager ({e:?}). Clipboard will not work.")
})
.ok();
let clipboard_data = manager.map(|manager| ClipboardData {
manager,
device: None,
source: None::<CopyPasteData<S>>,
});
let activation_state = ActivationState::bind(&global_list, &qh)
.inspect_err(|e| {
warn!("Could not bind xdg activation ({e:?}). Windows might not receive focus depending on compositor focus stealing policy.")
@ -518,7 +502,10 @@ impl<S: X11Selection> ServerState<NoConnection<S>> {
})
.ok();
let selection_states = selection::SelectionStates::new(&global_list, &qh);
dh.create_global::<InnerServerState<S>, XwaylandShellV1, _>(1, ());
global_list
.contents()
.with_list(|globals| handle_globals::<S>(&dh, globals));
@ -539,7 +526,7 @@ impl<S: X11Selection> ServerState<NoConnection<S>> {
xdg_wm_base,
viewporter,
fractional_scale,
clipboard_data,
selection_states,
last_kb_serial: None,
activation_state,
global_output_offset: GlobalOutputOffset {
@ -691,7 +678,7 @@ impl<C: XConnection> ServerState<C> {
self.unfocus = false;
}
self.handle_clipboard_events();
self.handle_selection_events();
self.handle_activations();
self.queue
.flush()
@ -1113,78 +1100,10 @@ impl<S: X11Selection + 'static> InnerServerState<S> {
}
}
pub(crate) fn set_copy_paste_source(&mut self, selection: &Rc<S>) {
if let Some(d) = &mut self.clipboard_data {
let src = d
.manager
.create_copy_paste_source(&self.qh, selection.mime_types());
let data = CopyPasteData::X11 {
inner: src,
data: Rc::downgrade(selection),
};
let CopyPasteData::X11 { inner, .. } = d.source.insert(data) else {
unreachable!();
};
if let Some(serial) = self
.last_kb_serial
.as_ref()
.map(|(_seat, serial)| serial)
.copied()
{
inner.set_selection(d.device.as_ref().unwrap(), serial);
}
}
}
pub fn new_global_scale(&mut self) -> Option<f64> {
self.new_scale.take()
}
pub fn new_selection(&mut self) -> Option<ForeignSelection> {
self.clipboard_data.as_mut().and_then(|c| {
c.source.take().and_then(|s| match s {
CopyPasteData::Foreign(f) => Some(f),
CopyPasteData::X11 { .. } => {
c.source = Some(s);
None
}
})
})
}
fn handle_clipboard_events(&mut self) {
if let Some(clipboard) = self.clipboard_data.as_mut() {
for (mime_type, fd) in std::mem::take(&mut self.world.selection_requests) {
let CopyPasteData::X11 { data, .. } = clipboard.source.as_ref().unwrap() else {
unreachable!("Got selection request without having set the selection?")
};
if let Some(data) = data.upgrade() {
data.write_to(&mime_type, fd);
}
}
if self.world.selection_cancelled {
clipboard.source = None;
self.world.selection_cancelled = false;
}
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;
}
}
}
}
}
fn handle_activations(&mut self) {
let Some(activation_state) = self.activation_state.as_ref() else {
return;
@ -1444,42 +1363,3 @@ pub struct PendingSurfaceState {
pub width: i32,
pub height: i32,
}
struct ClipboardData<X: X11Selection> {
manager: DataDeviceManagerState,
device: Option<DataDevice>,
source: Option<CopyPasteData<X>>,
}
pub struct ForeignSelection {
pub mime_types: Box<[String]>,
inner: SelectionOffer,
}
impl ForeignSelection {
pub(crate) fn receive(
&self,
mime_type: String,
state: &ServerState<impl XConnection>,
) -> Vec<u8> {
let mut pipe = self.inner.receive(mime_type).unwrap();
state.queue.flush().unwrap();
let mut data = Vec::new();
pipe.read_to_end(&mut data).unwrap();
data
}
}
impl Drop for ForeignSelection {
fn drop(&mut self) {
self.inner.destroy();
}
}
enum CopyPasteData<X: X11Selection> {
X11 {
inner: CopyPasteSource,
data: Weak<X>,
},
Foreign(ForeignSelection),
}

287
src/server/selection.rs Normal file
View file

@ -0,0 +1,287 @@
use super::clientside::SelectionEvents;
use super::{InnerServerState, MyWorld, ServerState};
use crate::{X11Selection, XConnection};
use log::{info, warn};
use smithay_client_toolkit::data_device_manager::ReadPipe;
use wayland_client::globals::GlobalList;
use wayland_client::protocol::wl_seat::WlSeat;
use wayland_client::{Proxy, QueueHandle};
use smithay_client_toolkit::data_device_manager::{
data_device::DataDevice, data_offer::SelectionOffer as WlSelectionOffer,
data_source::CopyPasteSource, DataDeviceManagerState,
};
use smithay_client_toolkit::primary_selection::device::PrimarySelectionDevice;
use smithay_client_toolkit::primary_selection::offer::PrimarySelectionOffer;
use smithay_client_toolkit::primary_selection::selection::PrimarySelectionSource;
use smithay_client_toolkit::primary_selection::PrimarySelectionManagerState;
use std::io::Read;
use std::rc::{Rc, Weak};
pub(super) struct SelectionStates<S: X11Selection> {
clipboard: Option<SelectionState<S, Clipboard>>,
primary: Option<SelectionState<S, Primary>>,
}
impl<S: X11Selection> SelectionStates<S> {
pub fn new(global_list: &GlobalList, qh: &QueueHandle<MyWorld>) -> Self {
Self {
clipboard: DataDeviceManagerState::bind(global_list, qh)
.inspect_err(|e| {
warn!("Could not bind data device manager ({e:?}). Clipboard will not work.")
})
.ok()
.map(SelectionState::new),
primary: PrimarySelectionManagerState::bind(global_list, qh)
.inspect_err(|_| info!("Primary selection unsupported."))
.ok()
.map(SelectionState::new),
}
}
pub fn seat_created(&mut self, qh: &QueueHandle<MyWorld>, seat: &WlSeat) {
if let Some(c) = &mut self.clipboard {
c.device = Some(c.manager.get_data_device(qh, seat));
}
if let Some(d) = &mut self.primary {
d.device = Some(d.manager.get_selection_device(qh, seat));
}
}
}
enum SelectionData<S: X11Selection, T: SelectionType> {
X11 { inner: T::Source, data: Weak<S> },
Foreign(ForeignSelection<T>),
}
struct SelectionState<S: X11Selection, T: SelectionType> {
manager: T::Manager,
device: Option<T::DataDevice>,
source: Option<SelectionData<S, T>>,
}
impl<S: X11Selection, T: SelectionType> SelectionState<S, T> {
fn new(manager: T::Manager) -> Self {
Self {
manager,
device: None,
source: None,
}
}
}
impl<S: X11Selection> InnerServerState<S> {
pub(super) fn handle_selection_events(&mut self) {
self.handle_impl::<Clipboard>();
self.handle_impl::<Primary>();
}
fn handle_impl<T: SelectionType>(&mut self) {
let Some(state) = T::selection_state(&mut self.selection_states) else {
return;
};
let events = T::get_events(&mut self.world);
for (mime_type, fd) in std::mem::take(&mut events.requests) {
let SelectionData::X11 { data, .. } = state.source.as_ref().unwrap() else {
unreachable!("Got selection request without having set the selection?")
};
if let Some(data) = data.upgrade() {
data.write_to(&mime_type, fd);
}
}
if events.cancelled {
state.source = None;
events.cancelled = false;
}
if state.source.is_none() {
if let Some(offer) = T::take_offer(&mut events.offer) {
let mime_types = T::get_mimes(&offer);
let foreign = ForeignSelection {
mime_types,
inner: offer,
};
state.source = Some(SelectionData::Foreign(foreign));
}
}
}
pub(crate) fn set_selection_source<T: SelectionType>(&mut self, selection: &Rc<S>) {
if let Some(state) = T::selection_state(&mut self.selection_states) {
let src = T::create_source(&state.manager, &self.qh, selection.mime_types());
let data = SelectionData::X11 {
inner: src,
data: Rc::downgrade(selection),
};
let SelectionData::X11 { inner, .. } = state.source.insert(data) else {
unreachable!();
};
if let Some(serial) = self
.last_kb_serial
.as_ref()
.map(|(_seat, serial)| serial)
.copied()
{
T::set_selection(inner, state.device.as_ref().unwrap(), serial);
}
}
}
pub(crate) fn new_selection<T: SelectionType>(&mut self) -> Option<ForeignSelection<T>> {
T::selection_state(&mut self.selection_states)
.as_mut()
.and_then(|state| {
state.source.take().and_then(|s| match s {
SelectionData::Foreign(f) => Some(f),
SelectionData::X11 { .. } => {
state.source = Some(s);
None
}
})
})
}
}
pub struct ForeignSelection<T: SelectionType> {
pub mime_types: Box<[String]>,
inner: T::Offer,
}
#[allow(private_bounds)]
impl<T: SelectionType> ForeignSelection<T> {
pub(crate) fn receive(
&self,
mime_type: String,
state: &ServerState<impl XConnection>,
) -> Vec<u8> {
let mut pipe = T::receive_offer(&self.inner, mime_type).unwrap();
state.queue.flush().unwrap();
let mut data = Vec::new();
pipe.read_to_end(&mut data).unwrap();
data
}
}
#[allow(private_bounds, private_interfaces)]
pub trait SelectionType: Sized {
type Source;
type Offer;
type Manager;
type DataDevice;
// The methods in this trait shouldn't be used outside of this file.
fn selection_state<S: X11Selection>(
state: &mut SelectionStates<S>,
) -> &mut Option<SelectionState<S, Self>>;
fn create_source(
manager: &Self::Manager,
qh: &QueueHandle<MyWorld>,
mime_types: Vec<&str>,
) -> Self::Source;
fn set_selection(source: &Self::Source, device: &Self::DataDevice, serial: u32);
fn get_events(world: &mut MyWorld) -> &mut SelectionEvents<Self::Offer>;
fn receive_offer(offer: &Self::Offer, mime_type: String) -> std::io::Result<ReadPipe>;
fn take_offer(offer: &mut Option<Self::Offer>) -> Option<Self::Offer> {
offer.take()
}
fn get_mimes(offer: &Self::Offer) -> Box<[String]>;
}
pub enum Clipboard {}
pub enum Primary {}
#[allow(private_bounds, private_interfaces)]
impl SelectionType for Clipboard {
type Source = CopyPasteSource;
type Offer = WlSelectionOffer;
type Manager = DataDeviceManagerState;
type DataDevice = DataDevice;
fn selection_state<S: X11Selection>(
state: &mut SelectionStates<S>,
) -> &mut Option<SelectionState<S, Self>> {
&mut state.clipboard
}
fn create_source(
manager: &Self::Manager,
qh: &QueueHandle<MyWorld>,
mime_types: Vec<&str>,
) -> Self::Source {
manager.create_copy_paste_source(qh, mime_types)
}
fn set_selection(source: &Self::Source, device: &Self::DataDevice, serial: u32) {
source.set_selection(device, serial);
}
fn get_events(world: &mut MyWorld) -> &mut SelectionEvents<Self::Offer> {
&mut world.clipboard
}
fn take_offer(offer: &mut Option<Self::Offer>) -> Option<Self::Offer> {
offer.take().filter(|offer| offer.inner().is_alive())
}
fn get_mimes(offer: &Self::Offer) -> Box<[String]> {
offer.with_mime_types(|mimes| mimes.into())
}
fn receive_offer(offer: &Self::Offer, mime_type: String) -> std::io::Result<ReadPipe> {
offer.receive(mime_type).map_err(|e| {
match e {
smithay_client_toolkit::data_device_manager::data_offer::DataOfferError::InvalidReceive => std::io::Error::from(std::io::ErrorKind::Other),
smithay_client_toolkit::data_device_manager::data_offer::DataOfferError::Io(e) => e
}
})
}
}
#[allow(private_bounds, private_interfaces)]
impl SelectionType for Primary {
type Source = PrimarySelectionSource;
type Offer = PrimarySelectionOffer;
type Manager = PrimarySelectionManagerState;
type DataDevice = PrimarySelectionDevice;
fn selection_state<S: X11Selection>(
state: &mut SelectionStates<S>,
) -> &mut Option<SelectionState<S, Self>> {
&mut state.primary
}
fn create_source(
manager: &Self::Manager,
qh: &QueueHandle<MyWorld>,
mime_types: Vec<&str>,
) -> Self::Source {
manager.create_selection_source(qh, mime_types)
}
fn set_selection(source: &Self::Source, device: &Self::DataDevice, serial: u32) {
source.set_selection(device, serial);
}
fn get_events(world: &mut MyWorld) -> &mut SelectionEvents<Self::Offer> {
&mut world.primary
}
fn receive_offer(offer: &Self::Offer, mime_type: String) -> std::io::Result<ReadPipe> {
offer.receive(mime_type)
}
fn get_mimes(offer: &Self::Offer) -> Box<[String]> {
offer.with_mime_types(|mimes| mimes.into())
}
}

View file

@ -1,4 +1,5 @@
use super::{InnerServerState, NoConnection, ServerState, WindowDims};
use super::{selection::Clipboard, InnerServerState, NoConnection, ServerState, WindowDims};
use crate::server::selection::{Primary, SelectionType};
use crate::xstate::{SetState, WinSize, WmName};
use crate::XConnection;
use rustix::event::{poll, PollFd, PollFlags};
@ -7,6 +8,7 @@ use std::io::Write;
use std::os::fd::{AsRawFd, BorrowedFd};
use std::os::unix::net::UnixStream;
use std::sync::{Arc, Mutex};
use testwl::SendDataForMimeFn;
use wayland_client::{
backend::{protocol::Message, Backend, ObjectData, ObjectId, WaylandError},
protocol::{
@ -1267,8 +1269,70 @@ fn window_group_properties() {
assert_eq!(data.toplevel().app_id, Some("class".into()));
}
#[test]
fn copy_from_x11() {
trait SelectionTest {
type SelectionType: SelectionType;
fn mimes(testwl: &mut testwl::Server) -> Vec<String>;
fn paste_data(
testwl: &mut testwl::Server,
send_data: impl SendDataForMimeFn,
) -> Vec<testwl::PasteData>;
fn create_offer(testwl: &mut testwl::Server, data: Vec<testwl::PasteData>);
}
macro_rules! selection_tests {
($name:ident, $selection_type:ty, $get_mime_fn:ident, $get_paste_data_fn:ident, $create_offer_fn:ident) => {
impl SelectionTest for $selection_type {
type SelectionType = $selection_type;
fn mimes(testwl: &mut testwl::Server) -> Vec<String> {
testwl.$get_mime_fn()
}
fn paste_data(
testwl: &mut testwl::Server,
send_data: impl SendDataForMimeFn,
) -> Vec<testwl::PasteData> {
testwl.$get_paste_data_fn(send_data)
}
fn create_offer(testwl: &mut testwl::Server, data: Vec<testwl::PasteData>) {
testwl.$create_offer_fn(data);
}
}
mod $name {
use super::*;
#[test]
fn copy_from_x11() {
super::copy_from_x11::<$selection_type>();
}
#[test]
fn copy_from_wayland() {
super::copy_from_wayland::<$selection_type>();
}
#[test]
fn x11_then_wayland() {
super::selection_x11_then_wayland::<$selection_type>();
}
}
};
}
selection_tests!(
clipboard,
Clipboard,
data_source_mimes,
clipboard_paste_data,
create_data_offer
);
selection_tests!(
primary,
Primary,
primary_source_mimes,
primary_paste_data,
create_primary_offer
);
fn copy_from_x11<T: SelectionTest>() {
let (mut f, comp) = TestFixture::new_with_compositor();
let win = unsafe { Window::new(1) };
let (_surface, _id) = f.create_toplevel(&comp, win);
@ -1284,23 +1348,22 @@ fn copy_from_x11() {
},
]);
f.satellite.set_copy_paste_source(&mimes);
f.satellite.set_selection_source::<T::SelectionType>(&mimes);
f.run();
let server_mimes = f.testwl.data_source_mimes();
let server_mimes = T::mimes(&mut f.testwl);
for mime in mimes.iter() {
assert!(server_mimes.contains(&mime.mime_type));
}
let data = f.testwl.paste_data(|_, _| {
let data = T::paste_data(&mut f.testwl, |_, _| {
f.satellite.run();
true
});
assert_eq!(*mimes, data);
}
#[test]
fn copy_from_wayland() {
fn copy_from_wayland<T: SelectionTest>() {
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) };
@ -1316,10 +1379,13 @@ fn copy_from_wayland() {
data: vec![1, 2, 3, 4, 6, 10],
},
];
f.testwl.create_data_offer(mimes.clone());
T::create_offer(&mut f.testwl, mimes.clone());
f.run();
let selection = f.satellite.new_selection().expect("No new selection");
let selection = f
.satellite
.new_selection::<T::SelectionType>()
.expect("No new selection");
for mime in &mimes {
let data = std::thread::scope(|s| {
// receive requires a queue flush - dispatch testwl from another thread
@ -1341,8 +1407,7 @@ fn copy_from_wayland() {
}
}
#[test]
fn clipboard_x11_then_wayland() {
fn selection_x11_then_wayland<T: SelectionTest>() {
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) };
@ -1359,7 +1424,8 @@ fn clipboard_x11_then_wayland() {
},
]);
f.satellite.set_copy_paste_source(&x11data);
f.satellite
.set_selection_source::<T::SelectionType>(&x11data);
f.run();
let waylanddata = vec![
@ -1372,11 +1438,14 @@ fn clipboard_x11_then_wayland() {
data: vec![10, 20, 40, 50],
},
];
f.testwl.create_data_offer(waylanddata.clone());
T::create_offer(&mut f.testwl, waylanddata.clone());
f.run();
f.run();
let selection = f.satellite.new_selection().expect("No new selection");
let selection = f
.satellite
.new_selection::<T::SelectionType>()
.expect("No new selection");
for mime in &waylanddata {
let data = std::thread::scope(|s| {
// receive requires a queue flush - dispatch testwl from another thread
@ -2429,7 +2498,7 @@ fn quick_empty_data_offer() {
f.testwl.empty_data_offer();
f.run();
let selection = f.satellite.new_selection();
let selection = f.satellite.new_selection::<Clipboard>();
assert!(selection.is_none());
}