Support primary selection
This was more tedious than expected. Fixes #103
This commit is contained in:
parent
13469566b0
commit
5a184d4359
11 changed files with 1086 additions and 391 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
|
|
|
|||
|
|
@ -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
287
src/server/selection.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue