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
|
|
@ -5,6 +5,7 @@ use crate::server::{NoConnection, PendingSurfaceState, ServerState};
|
|||
use crate::xstate::{RealConnection, XState};
|
||||
use log::{error, info};
|
||||
use rustix::event::{poll, PollFd, PollFlags};
|
||||
use server::selection::{Clipboard, Primary};
|
||||
use smithay_client_toolkit::data_device_manager::WritePipe;
|
||||
use std::io::{BufRead, BufReader, Read, Write};
|
||||
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd};
|
||||
|
|
@ -212,10 +213,14 @@ pub fn main(mut data: impl RunData) -> Option<()> {
|
|||
server_state.run();
|
||||
display.flush_clients().unwrap();
|
||||
|
||||
if let Some(sel) = server_state.new_selection() {
|
||||
if let Some(sel) = server_state.new_selection::<Clipboard>() {
|
||||
xstate.set_clipboard(sel);
|
||||
}
|
||||
|
||||
if let Some(sel) = server_state.new_selection::<Primary>() {
|
||||
xstate.set_primary_selection(sel);
|
||||
}
|
||||
|
||||
if let Some(scale) = server_state.new_global_scale() {
|
||||
xstate.update_global_scale(scale);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
mod settings;
|
||||
use settings::Settings;
|
||||
mod selection;
|
||||
use selection::{Selection, SelectionData};
|
||||
use selection::{Selection, SelectionState};
|
||||
use wayland_protocols::xdg::decoration::zv1::client::zxdg_toplevel_decoration_v1;
|
||||
|
||||
use crate::XConnection;
|
||||
|
|
@ -117,7 +117,7 @@ pub struct XState {
|
|||
window_atoms: WindowTypes,
|
||||
root: x::Window,
|
||||
wm_window: x::Window,
|
||||
selection_data: SelectionData,
|
||||
selection_state: SelectionState,
|
||||
settings: Settings,
|
||||
}
|
||||
|
||||
|
|
@ -201,6 +201,15 @@ impl XState {
|
|||
| SelectionEventMask::SELECTION_CLIENT_CLOSE,
|
||||
})
|
||||
.unwrap();
|
||||
connection
|
||||
.send_and_check_request(&xcb::xfixes::SelectSelectionInput {
|
||||
window: root,
|
||||
selection: atoms.primary,
|
||||
event_mask: SelectionEventMask::SET_SELECTION_OWNER
|
||||
| SelectionEventMask::SELECTION_WINDOW_DESTROY
|
||||
| SelectionEventMask::SELECTION_CLIENT_CLOSE,
|
||||
})
|
||||
.unwrap();
|
||||
{
|
||||
// Setup default cursor theme
|
||||
let ctx = CursorContext::new(&connection, screen).unwrap();
|
||||
|
|
@ -214,7 +223,7 @@ impl XState {
|
|||
}
|
||||
|
||||
let wm_window = connection.generate_id();
|
||||
let selection_data = SelectionData::new(&connection, root);
|
||||
let selection_state = SelectionState::new(&connection, root, &atoms);
|
||||
let window_atoms = WindowTypes::intern_all(&connection).unwrap();
|
||||
let settings = Settings::new(&connection, &atoms, root);
|
||||
|
||||
|
|
@ -224,7 +233,7 @@ impl XState {
|
|||
root,
|
||||
atoms,
|
||||
window_atoms,
|
||||
selection_data,
|
||||
selection_state,
|
||||
settings,
|
||||
};
|
||||
r.create_ewmh_window();
|
||||
|
|
@ -917,6 +926,7 @@ xcb::atoms_struct! {
|
|||
incr => b"INCR" only_if_exists = false,
|
||||
xsettings => b"_XSETTINGS_S0" only_if_exists = false,
|
||||
xsettings_settings => b"_XSETTINGS_SETTINGS" only_if_exists = false,
|
||||
primary => b"PRIMARY" only_if_exists = false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use super::{get_atom_name, XState};
|
||||
use crate::server::ForeignSelection;
|
||||
use crate::server::selection::{Clipboard, ForeignSelection, Primary, SelectionType};
|
||||
use crate::{RealServerState, X11Selection};
|
||||
use log::{debug, error, warn};
|
||||
use smithay_client_toolkit::data_device_manager::WritePipe;
|
||||
|
|
@ -26,7 +26,7 @@ pub struct Selection {
|
|||
connection: Rc<xcb::Connection>,
|
||||
window: x::Window,
|
||||
pending: RefCell<Vec<PendingSelectionData>>,
|
||||
clipboard: x::Atom,
|
||||
selection: x::Atom,
|
||||
selection_time: u32,
|
||||
incr: x::Atom,
|
||||
}
|
||||
|
|
@ -46,13 +46,13 @@ impl X11Selection for Selection {
|
|||
.connection
|
||||
.send_and_check_request(&x::ConvertSelection {
|
||||
requestor: self.window,
|
||||
selection: self.clipboard,
|
||||
selection: self.selection,
|
||||
target: target.atom,
|
||||
property: target.atom,
|
||||
time: self.selection_time,
|
||||
})
|
||||
{
|
||||
error!("Failed to request clipboard data (mime type: {mime}, error: {e})");
|
||||
error!("Failed to request selection data (mime type: {mime}, error: {e})");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -162,21 +162,275 @@ impl Selection {
|
|||
}
|
||||
}
|
||||
|
||||
enum CurrentSelection {
|
||||
enum CurrentSelection<T: SelectionType> {
|
||||
X11(Rc<Selection>),
|
||||
Wayland {
|
||||
mimes: Vec<SelectionTargetId>,
|
||||
inner: ForeignSelection,
|
||||
inner: ForeignSelection<T>,
|
||||
},
|
||||
}
|
||||
pub(crate) struct SelectionData {
|
||||
|
||||
struct SelectionData<T: SelectionType> {
|
||||
last_selection_timestamp: u32,
|
||||
target_window: x::Window,
|
||||
current_selection: Option<CurrentSelection>,
|
||||
atom: x::Atom,
|
||||
current_selection: Option<CurrentSelection<T>>,
|
||||
}
|
||||
|
||||
impl SelectionData {
|
||||
pub fn new(connection: &xcb::Connection, root: x::Window) -> Self {
|
||||
// This is a trait so that we can use &dyn
|
||||
trait SelectionDataImpl {
|
||||
fn set_owner(&self, connection: &xcb::Connection, wm_window: x::Window);
|
||||
fn handle_new_owner(
|
||||
&mut self,
|
||||
connection: &xcb::Connection,
|
||||
wm_window: x::Window,
|
||||
atoms: &super::Atoms,
|
||||
owner: x::Window,
|
||||
timestamp: u32,
|
||||
);
|
||||
fn handle_target_list(
|
||||
&mut self,
|
||||
connection: &Rc<xcb::Connection>,
|
||||
wm_window: x::Window,
|
||||
atoms: &super::Atoms,
|
||||
target_window: x::Window,
|
||||
dest_property: x::Atom,
|
||||
server_state: &mut RealServerState,
|
||||
);
|
||||
fn x11_selection(&self) -> Option<&Selection>;
|
||||
fn handle_selection_request(
|
||||
&self,
|
||||
connection: &xcb::Connection,
|
||||
atoms: &super::Atoms,
|
||||
request: &x::SelectionRequestEvent,
|
||||
success: &dyn Fn(),
|
||||
refuse: &dyn Fn(),
|
||||
server_state: &mut RealServerState,
|
||||
);
|
||||
fn atom(&self) -> x::Atom;
|
||||
}
|
||||
|
||||
impl<T: SelectionType> SelectionData<T> {
|
||||
fn new(atom: x::Atom) -> Self {
|
||||
Self {
|
||||
last_selection_timestamp: x::CURRENT_TIME,
|
||||
atom,
|
||||
current_selection: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: SelectionType> SelectionDataImpl for SelectionData<T> {
|
||||
fn atom(&self) -> x::Atom {
|
||||
self.atom
|
||||
}
|
||||
fn set_owner(&self, connection: &xcb::Connection, wm_window: x::Window) {
|
||||
connection
|
||||
.send_and_check_request(&x::SetSelectionOwner {
|
||||
owner: wm_window,
|
||||
selection: self.atom,
|
||||
time: self.last_selection_timestamp,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let reply = connection
|
||||
.wait_for_reply(connection.send_request(&x::GetSelectionOwner {
|
||||
selection: self.atom,
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
if reply.owner() != wm_window {
|
||||
warn!(
|
||||
"Could not get {} selection (owned by {:?})",
|
||||
get_atom_name(connection, self.atom),
|
||||
reply.owner()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_new_owner(
|
||||
&mut self,
|
||||
connection: &xcb::Connection,
|
||||
wm_window: x::Window,
|
||||
atoms: &super::Atoms,
|
||||
owner: x::Window,
|
||||
timestamp: u32,
|
||||
) {
|
||||
debug!(
|
||||
"new {} owner: {owner:?}",
|
||||
get_atom_name(connection, self.atom)
|
||||
);
|
||||
self.last_selection_timestamp = timestamp;
|
||||
// Grab targets
|
||||
connection
|
||||
.send_and_check_request(&x::ConvertSelection {
|
||||
requestor: wm_window,
|
||||
selection: self.atom,
|
||||
target: atoms.targets,
|
||||
property: atoms.selection_reply,
|
||||
time: timestamp,
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn handle_target_list(
|
||||
&mut self,
|
||||
connection: &Rc<xcb::Connection>,
|
||||
wm_window: x::Window,
|
||||
atoms: &super::Atoms,
|
||||
target_window: x::Window,
|
||||
dest_property: x::Atom,
|
||||
server_state: &mut RealServerState,
|
||||
) {
|
||||
let reply = connection
|
||||
.wait_for_reply(connection.send_request(&x::GetProperty {
|
||||
delete: true,
|
||||
window: wm_window,
|
||||
property: dest_property,
|
||||
r#type: x::ATOM_ATOM,
|
||||
long_offset: 0,
|
||||
long_length: 20,
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
let targets: &[x::Atom] = reply.value();
|
||||
if targets.is_empty() {
|
||||
warn!("Got empty selection target list, trying again...");
|
||||
match connection.wait_for_reply(connection.send_request(&x::GetSelectionOwner {
|
||||
selection: self.atom,
|
||||
})) {
|
||||
Ok(reply) => {
|
||||
if reply.owner() == wm_window {
|
||||
warn!("We are unexpectedly the selection owner? Clipboard may be broken!");
|
||||
} else {
|
||||
self.handle_new_owner(
|
||||
connection,
|
||||
wm_window,
|
||||
atoms,
|
||||
reply.owner(),
|
||||
self.last_selection_timestamp,
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Couldn't grab selection owner: {e:?}. Clipboard is stale!");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if log::log_enabled!(log::Level::Debug) {
|
||||
let targets_str: Vec<String> = targets
|
||||
.iter()
|
||||
.map(|t| get_atom_name(connection, *t))
|
||||
.collect();
|
||||
debug!("got targets: {targets_str:?}");
|
||||
}
|
||||
|
||||
let mimes = targets
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|atom| ![atoms.targets, atoms.multiple, atoms.save_targets].contains(atom))
|
||||
.map(|target_atom| SelectionTargetId {
|
||||
name: get_atom_name(connection, target_atom),
|
||||
atom: target_atom,
|
||||
source: None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let selection = Rc::new(Selection {
|
||||
mimes,
|
||||
connection: connection.clone(),
|
||||
window: target_window,
|
||||
pending: RefCell::default(),
|
||||
selection: self.atom,
|
||||
selection_time: self.last_selection_timestamp,
|
||||
incr: atoms.incr,
|
||||
});
|
||||
|
||||
server_state.set_selection_source::<T>(&selection);
|
||||
self.current_selection = Some(CurrentSelection::X11(selection));
|
||||
debug!("{} set from X11", get_atom_name(connection, self.atom));
|
||||
}
|
||||
|
||||
fn x11_selection(&self) -> Option<&Selection> {
|
||||
match &self.current_selection {
|
||||
Some(CurrentSelection::X11(selection)) => Some(selection),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_selection_request(
|
||||
&self,
|
||||
connection: &xcb::Connection,
|
||||
atoms: &super::Atoms,
|
||||
request: &x::SelectionRequestEvent,
|
||||
success: &dyn Fn(),
|
||||
refuse: &dyn Fn(),
|
||||
server_state: &mut RealServerState,
|
||||
) {
|
||||
let Some(CurrentSelection::Wayland { mimes, inner }) = &self.current_selection else {
|
||||
warn!("Got selection request, but we don't seem to be the selection owner");
|
||||
refuse();
|
||||
return;
|
||||
};
|
||||
|
||||
match request.target() {
|
||||
x if x == atoms.targets => {
|
||||
let atoms: Box<[x::Atom]> = mimes.iter().map(|t| t.atom).collect();
|
||||
|
||||
connection
|
||||
.send_and_check_request(&x::ChangeProperty {
|
||||
mode: x::PropMode::Replace,
|
||||
window: request.requestor(),
|
||||
property: request.property(),
|
||||
r#type: x::ATOM_ATOM,
|
||||
data: &atoms,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
success();
|
||||
}
|
||||
other => {
|
||||
let Some(target) = mimes.iter().find(|t| t.atom == other) else {
|
||||
if log::log_enabled!(log::Level::Debug) {
|
||||
let name = get_atom_name(connection, other);
|
||||
debug!("refusing selection request because given atom could not be found ({name})");
|
||||
}
|
||||
refuse();
|
||||
return;
|
||||
};
|
||||
|
||||
let mime_name = target
|
||||
.source
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.unwrap_or_else(|| target.name.clone());
|
||||
let data = inner.receive(mime_name, server_state);
|
||||
match connection.send_and_check_request(&x::ChangeProperty {
|
||||
mode: x::PropMode::Replace,
|
||||
window: request.requestor(),
|
||||
property: request.property(),
|
||||
r#type: target.atom,
|
||||
data: &data,
|
||||
}) {
|
||||
Ok(_) => success(),
|
||||
Err(e) => {
|
||||
warn!("Failed setting selection property: {e:?}");
|
||||
refuse();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct SelectionState {
|
||||
clipboard: SelectionData<Clipboard>,
|
||||
primary: SelectionData<Primary>,
|
||||
target_window: x::Window,
|
||||
}
|
||||
|
||||
impl SelectionState {
|
||||
pub fn new(connection: &xcb::Connection, root: x::Window, atoms: &super::Atoms) -> Self {
|
||||
let target_window = connection.generate_id();
|
||||
connection
|
||||
.send_and_check_request(&x::CreateWindow {
|
||||
|
|
@ -195,39 +449,15 @@ impl SelectionData {
|
|||
})
|
||||
.expect("Couldn't create window for selections");
|
||||
Self {
|
||||
last_selection_timestamp: x::CURRENT_TIME,
|
||||
target_window,
|
||||
current_selection: None,
|
||||
clipboard: SelectionData::new(atoms.clipboard),
|
||||
primary: SelectionData::new(atoms.primary),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl XState {
|
||||
fn set_clipboard_owner(&mut self) {
|
||||
self.connection
|
||||
.send_and_check_request(&x::SetSelectionOwner {
|
||||
owner: self.wm_window,
|
||||
selection: self.atoms.clipboard,
|
||||
time: self.selection_data.last_selection_timestamp,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let reply = self
|
||||
.connection
|
||||
.wait_for_reply(self.connection.send_request(&x::GetSelectionOwner {
|
||||
selection: self.atoms.clipboard,
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
if reply.owner() != self.wm_window {
|
||||
warn!(
|
||||
"Could not get CLIPBOARD selection (owned by {:?})",
|
||||
reply.owner()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_clipboard(&mut self, selection: ForeignSelection) {
|
||||
pub(crate) fn set_clipboard(&mut self, selection: ForeignSelection<Clipboard>) {
|
||||
let mut utf8_xwl = false;
|
||||
let mut utf8_wl = false;
|
||||
let mut mimes: Vec<SelectionTargetId> = selection
|
||||
|
|
@ -273,51 +503,133 @@ impl XState {
|
|||
});
|
||||
}
|
||||
|
||||
self.selection_data.current_selection = Some(CurrentSelection::Wayland {
|
||||
self.selection_state.clipboard.current_selection = Some(CurrentSelection::Wayland {
|
||||
mimes,
|
||||
inner: selection,
|
||||
});
|
||||
self.set_clipboard_owner();
|
||||
self.selection_state
|
||||
.clipboard
|
||||
.set_owner(&self.connection, self.wm_window);
|
||||
debug!("Clipboard set from Wayland");
|
||||
}
|
||||
|
||||
pub(crate) fn set_primary_selection(&mut self, selection: ForeignSelection<Primary>) {
|
||||
let mut utf8_xwl = false;
|
||||
let mut utf8_wl = false;
|
||||
let mut mimes: Vec<SelectionTargetId> = selection
|
||||
.mime_types
|
||||
.iter()
|
||||
.map(|mime| {
|
||||
match mime.as_str() {
|
||||
"UTF8_STRING" => utf8_xwl = true,
|
||||
"text/plain;charset=utf-8" => utf8_wl = true,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let atom = self
|
||||
.connection
|
||||
.wait_for_reply(self.connection.send_request(&x::InternAtom {
|
||||
only_if_exists: false,
|
||||
name: mime.as_bytes(),
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
SelectionTargetId {
|
||||
name: mime.clone(),
|
||||
atom: atom.atom(),
|
||||
source: None,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if utf8_wl && !utf8_xwl {
|
||||
let name = "UTF8_STRING".to_string();
|
||||
let atom = self
|
||||
.connection
|
||||
.wait_for_reply(self.connection.send_request(&x::InternAtom {
|
||||
only_if_exists: false,
|
||||
name: name.as_bytes(),
|
||||
}))
|
||||
.unwrap()
|
||||
.atom();
|
||||
mimes.push(SelectionTargetId {
|
||||
name,
|
||||
atom,
|
||||
source: Some("text/plain;charset=utf-8".to_string()),
|
||||
});
|
||||
}
|
||||
|
||||
self.selection_state.primary.current_selection = Some(CurrentSelection::Wayland {
|
||||
mimes,
|
||||
inner: selection,
|
||||
});
|
||||
self.selection_state
|
||||
.primary
|
||||
.set_owner(&self.connection, self.wm_window);
|
||||
debug!("Primaryset from Wayland");
|
||||
}
|
||||
|
||||
pub(super) fn handle_selection_event(
|
||||
&mut self,
|
||||
event: &xcb::Event,
|
||||
server_state: &mut RealServerState,
|
||||
) -> bool {
|
||||
macro_rules! get_selection_data {
|
||||
($selection:expr) => {
|
||||
match $selection {
|
||||
x if x == self.atoms.clipboard => {
|
||||
&mut self.selection_state.clipboard as &mut dyn SelectionDataImpl
|
||||
}
|
||||
x if x == self.atoms.primary => &mut self.selection_state.primary as _,
|
||||
_ => return true,
|
||||
}
|
||||
};
|
||||
}
|
||||
match event {
|
||||
xcb::Event::X(x::Event::SelectionClear(e)) => {
|
||||
if e.selection() == self.atoms.clipboard {
|
||||
self.handle_new_selection_owner(e.owner(), e.time());
|
||||
}
|
||||
let data = get_selection_data!(e.selection());
|
||||
data.handle_new_owner(
|
||||
&self.connection,
|
||||
self.wm_window,
|
||||
&self.atoms,
|
||||
e.owner(),
|
||||
e.time(),
|
||||
);
|
||||
}
|
||||
xcb::Event::X(x::Event::SelectionNotify(e)) => {
|
||||
if e.property() == x::ATOM_NONE {
|
||||
warn!("selection notify fail?");
|
||||
warn!(
|
||||
"selection notify fail? {}",
|
||||
get_atom_name(&self.connection, e.selection())
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
let data = get_selection_data!(e.selection());
|
||||
debug!(
|
||||
"selection notify requestor: {:?} target: {}",
|
||||
"selection notify requestor: {:?} target: {} selection: {}",
|
||||
e.requestor(),
|
||||
get_atom_name(&self.connection, e.target())
|
||||
get_atom_name(&self.connection, e.target()),
|
||||
get_atom_name(&self.connection, e.selection()),
|
||||
);
|
||||
|
||||
if e.requestor() == self.wm_window {
|
||||
match e.target() {
|
||||
x if x == self.atoms.targets => {
|
||||
self.handle_target_list(e.property(), server_state)
|
||||
}
|
||||
x if x == self.atoms.targets => data.handle_target_list(
|
||||
&self.connection,
|
||||
self.wm_window,
|
||||
&self.atoms,
|
||||
self.selection_state.target_window,
|
||||
e.property(),
|
||||
server_state,
|
||||
),
|
||||
other => warn!(
|
||||
"got unexpected selection notify for target {}",
|
||||
get_atom_name(&self.connection, other)
|
||||
),
|
||||
}
|
||||
} else if e.requestor() == self.selection_data.target_window {
|
||||
if let Some(CurrentSelection::X11(selection)) =
|
||||
&self.selection_data.current_selection
|
||||
{
|
||||
} else if e.requestor() == self.selection_state.target_window {
|
||||
if let Some(selection) = data.x11_selection() {
|
||||
selection.handle_notify(e.target());
|
||||
}
|
||||
} else {
|
||||
|
|
@ -328,6 +640,7 @@ impl XState {
|
|||
}
|
||||
}
|
||||
xcb::Event::X(x::Event::SelectionRequest(e)) => {
|
||||
let data = get_selection_data!(e.selection());
|
||||
let send_notify = |property| {
|
||||
self.connection
|
||||
.send_and_check_request(&x::SendEvent {
|
||||
|
|
@ -349,7 +662,8 @@ impl XState {
|
|||
|
||||
if log::log_enabled!(log::Level::Debug) {
|
||||
let target = get_atom_name(&self.connection, e.target());
|
||||
debug!("Got selection request for target {target}");
|
||||
let selection = get_atom_name(&self.connection, data.atom());
|
||||
debug!("Got selection request for target {target} (selection: {selection})");
|
||||
}
|
||||
|
||||
if e.property() == x::ATOM_NONE {
|
||||
|
|
@ -358,76 +672,37 @@ impl XState {
|
|||
return true;
|
||||
}
|
||||
|
||||
let Some(CurrentSelection::Wayland { mimes, inner }) =
|
||||
&self.selection_data.current_selection
|
||||
else {
|
||||
warn!("Got selection request, but we don't seem to be the selection owner");
|
||||
refuse();
|
||||
return true;
|
||||
};
|
||||
|
||||
match e.target() {
|
||||
x if x == self.atoms.targets => {
|
||||
let atoms: Box<[x::Atom]> = mimes.iter().map(|t| t.atom).collect();
|
||||
|
||||
self.connection
|
||||
.send_and_check_request(&x::ChangeProperty {
|
||||
mode: x::PropMode::Replace,
|
||||
window: e.requestor(),
|
||||
property: e.property(),
|
||||
r#type: x::ATOM_ATOM,
|
||||
data: &atoms,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
success();
|
||||
}
|
||||
other => {
|
||||
let Some(target) = mimes.iter().find(|t| t.atom == other) else {
|
||||
if log::log_enabled!(log::Level::Debug) {
|
||||
let name = get_atom_name(&self.connection, other);
|
||||
debug!("refusing selection request because given atom could not be found ({name})");
|
||||
}
|
||||
refuse();
|
||||
return true;
|
||||
};
|
||||
|
||||
let mime_name = target
|
||||
.source
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.unwrap_or_else(|| target.name.clone());
|
||||
let data = inner.receive(mime_name, server_state);
|
||||
match self.connection.send_and_check_request(&x::ChangeProperty {
|
||||
mode: x::PropMode::Replace,
|
||||
window: e.requestor(),
|
||||
property: e.property(),
|
||||
r#type: target.atom,
|
||||
data: &data,
|
||||
}) {
|
||||
Ok(_) => success(),
|
||||
Err(e) => {
|
||||
warn!("Failed setting selection property: {e:?}");
|
||||
refuse();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
data.handle_selection_request(
|
||||
&self.connection,
|
||||
&self.atoms,
|
||||
e,
|
||||
&success,
|
||||
&refuse,
|
||||
server_state,
|
||||
);
|
||||
}
|
||||
|
||||
xcb::Event::XFixes(xcb::xfixes::Event::SelectionNotify(e)) => match e.selection() {
|
||||
x if x == self.atoms.clipboard => match e.subtype() {
|
||||
x if x == self.atoms.clipboard || x == self.atoms.primary => match e.subtype() {
|
||||
xcb::xfixes::SelectionEvent::SetSelectionOwner => {
|
||||
if e.owner() == self.wm_window {
|
||||
return true;
|
||||
}
|
||||
|
||||
self.handle_new_selection_owner(e.owner(), e.selection_timestamp());
|
||||
let data = get_selection_data!(x);
|
||||
|
||||
data.handle_new_owner(
|
||||
&self.connection,
|
||||
self.wm_window,
|
||||
&self.atoms,
|
||||
e.owner(),
|
||||
e.timestamp(),
|
||||
);
|
||||
}
|
||||
xcb::xfixes::SelectionEvent::SelectionClientClose
|
||||
| xcb::xfixes::SelectionEvent::SelectionWindowDestroy => {
|
||||
debug!("Selection owner destroyed, selection will be unset");
|
||||
self.selection_data.current_selection = None;
|
||||
self.selection_state.clipboard.current_selection = None;
|
||||
}
|
||||
},
|
||||
x if x == self.atoms.xsettings => match e.subtype() {
|
||||
|
|
@ -446,105 +721,17 @@ impl XState {
|
|||
true
|
||||
}
|
||||
|
||||
fn handle_new_selection_owner(&mut self, owner: x::Window, timestamp: u32) {
|
||||
debug!("new selection owner: {owner:?}");
|
||||
self.selection_data.last_selection_timestamp = timestamp;
|
||||
// Grab targets
|
||||
self.connection
|
||||
.send_and_check_request(&x::ConvertSelection {
|
||||
requestor: self.wm_window,
|
||||
selection: self.atoms.clipboard,
|
||||
target: self.atoms.targets,
|
||||
property: self.atoms.selection_reply,
|
||||
time: timestamp,
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn handle_target_list(&mut self, dest_property: x::Atom, server_state: &mut RealServerState) {
|
||||
let reply = self
|
||||
.connection
|
||||
.wait_for_reply(self.connection.send_request(&x::GetProperty {
|
||||
delete: true,
|
||||
window: self.wm_window,
|
||||
property: dest_property,
|
||||
r#type: x::ATOM_ATOM,
|
||||
long_offset: 0,
|
||||
long_length: 20,
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
let targets: &[x::Atom] = reply.value();
|
||||
if targets.is_empty() {
|
||||
warn!("Got empty selection target list, trying again...");
|
||||
match self.connection.wait_for_reply(self.connection.send_request(
|
||||
&x::GetSelectionOwner {
|
||||
selection: self.atoms.clipboard,
|
||||
},
|
||||
)) {
|
||||
Ok(reply) => {
|
||||
if reply.owner() == self.wm_window {
|
||||
warn!("We are unexpectedly the selection owner? Clipboard may be broken!");
|
||||
} else {
|
||||
self.handle_new_selection_owner(
|
||||
reply.owner(),
|
||||
self.selection_data.last_selection_timestamp,
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Couldn't grab selection owner: {e:?}. Clipboard is stale!");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if log::log_enabled!(log::Level::Debug) {
|
||||
let targets_str: Vec<String> = targets
|
||||
.iter()
|
||||
.map(|t| get_atom_name(&self.connection, *t))
|
||||
.collect();
|
||||
debug!("got targets: {targets_str:?}");
|
||||
}
|
||||
|
||||
let mimes = targets
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|atom| {
|
||||
![
|
||||
self.atoms.targets,
|
||||
self.atoms.multiple,
|
||||
self.atoms.save_targets,
|
||||
]
|
||||
.contains(atom)
|
||||
})
|
||||
.map(|target_atom| SelectionTargetId {
|
||||
name: get_atom_name(&self.connection, target_atom),
|
||||
atom: target_atom,
|
||||
source: None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let selection = Rc::new(Selection {
|
||||
mimes,
|
||||
connection: self.connection.clone(),
|
||||
window: self.selection_data.target_window,
|
||||
pending: RefCell::default(),
|
||||
clipboard: self.atoms.clipboard,
|
||||
selection_time: self.selection_data.last_selection_timestamp,
|
||||
incr: self.atoms.incr,
|
||||
});
|
||||
|
||||
server_state.set_copy_paste_source(&selection);
|
||||
self.selection_data.current_selection = Some(CurrentSelection::X11(selection));
|
||||
debug!("Clipboard set from X11");
|
||||
}
|
||||
|
||||
pub(super) fn handle_selection_property_change(
|
||||
&mut self,
|
||||
event: &x::PropertyNotifyEvent,
|
||||
) -> bool {
|
||||
if let Some(CurrentSelection::X11(selection)) = &self.selection_data.current_selection {
|
||||
return selection.check_for_incr(event);
|
||||
for data in [
|
||||
&self.selection_state.primary as &dyn SelectionDataImpl,
|
||||
&self.selection_state.clipboard as _,
|
||||
] {
|
||||
if let Some(selection) = &data.x11_selection() {
|
||||
return selection.check_for_incr(event);
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1004,7 +1004,7 @@ fn copy_from_x11() {
|
|||
);
|
||||
}
|
||||
|
||||
let data = f.testwl.paste_data(|mime, _| {
|
||||
let data = f.testwl.clipboard_paste_data(|mime, _| {
|
||||
let request = connection.await_selection_request();
|
||||
let data = mimes_truth
|
||||
.iter()
|
||||
|
|
@ -1201,7 +1201,7 @@ fn bad_clipboard_data() {
|
|||
connection.send_selection_notify(&request);
|
||||
|
||||
f.wait_and_dispatch();
|
||||
let mut data = f.testwl.paste_data(|_, _| {
|
||||
let mut data = f.testwl.clipboard_paste_data(|_, _| {
|
||||
let request = connection.await_selection_request();
|
||||
assert_eq!(request.target(), connection.atoms.mime2);
|
||||
// Don't actually set any data as requested - just report success
|
||||
|
|
@ -1388,7 +1388,7 @@ fn incr_copy_from_x11() {
|
|||
.take(3000)
|
||||
.collect();
|
||||
let mut it = data.chunks(500).enumerate();
|
||||
let mut paste_data = f.testwl.paste_data(|_, testwl| {
|
||||
let mut paste_data = f.testwl.clipboard_paste_data(|_, testwl| {
|
||||
if let Some(begin) = begin_incr.take() {
|
||||
destination_property = begin(&mut connection);
|
||||
testwl.dispatch();
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ use std::os::fd::{AsFd, BorrowedFd, OwnedFd};
|
|||
use std::os::unix::net::UnixStream;
|
||||
use std::sync::{Arc, Mutex, OnceLock};
|
||||
use std::time::Instant;
|
||||
use wayland_protocols::wp::primary_selection::zv1::server::zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1;
|
||||
use wayland_protocols::wp::primary_selection::zv1::server::zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1;
|
||||
use wayland_protocols::wp::primary_selection::zv1::server::zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1;
|
||||
use wayland_protocols::wp::primary_selection::zv1::server::zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1;
|
||||
use wayland_protocols::{
|
||||
wp::{
|
||||
fractional_scale::v1::server::{
|
||||
|
|
@ -253,9 +257,12 @@ struct State {
|
|||
tablet: Option<ZwpTabletV2>,
|
||||
tablet_tool: Option<ZwpTabletToolV2>,
|
||||
configure_serial: u32,
|
||||
selection: Option<WlDataSource>,
|
||||
clipboard: Option<WlDataSource>,
|
||||
primary: Option<ZwpPrimarySelectionSourceV1>,
|
||||
data_device_man: Option<WlDataDeviceManager>,
|
||||
data_device: Option<WlDataDevice>,
|
||||
primary_man: Option<ZwpPrimarySelectionDeviceManagerV1>,
|
||||
primary_device: Option<ZwpPrimarySelectionDeviceV1>,
|
||||
xdg_activation: Option<XdgActivationV1>,
|
||||
valid_tokens: HashSet<String>,
|
||||
token_counter: u32,
|
||||
|
|
@ -279,7 +286,10 @@ impl Default for State {
|
|||
tablet: None,
|
||||
tablet_tool: None,
|
||||
configure_serial: 0,
|
||||
selection: None,
|
||||
clipboard: None,
|
||||
primary: None,
|
||||
primary_man: None,
|
||||
primary_device: None,
|
||||
data_device_man: None,
|
||||
data_device: None,
|
||||
xdg_activation: None,
|
||||
|
|
@ -404,6 +414,9 @@ pub struct Server {
|
|||
client: Option<Client>,
|
||||
}
|
||||
|
||||
pub trait SendDataForMimeFn: FnMut(&str, &mut Server) -> bool {}
|
||||
impl<T> SendDataForMimeFn for T where T: FnMut(&str, &mut Server) -> bool {}
|
||||
|
||||
impl Server {
|
||||
pub fn new(noops: bool) -> Self {
|
||||
let display = Display::new().unwrap();
|
||||
|
|
@ -436,6 +449,7 @@ impl Server {
|
|||
dh.create_global::<State, XdgWmBase, _>(6, ());
|
||||
dh.create_global::<State, WlSeat, _>(5, ());
|
||||
dh.create_global::<State, WlDataDeviceManager, _>(3, ());
|
||||
dh.create_global::<State, ZwpPrimarySelectionDeviceManagerV1, _>(1, ());
|
||||
dh.create_global::<State, ZwpTabletManagerV2, _>(1, ());
|
||||
dh.create_global::<State, XdgActivationV1, _>(1, ());
|
||||
dh.create_global::<State, ZxdgDecorationManagerV1, _>(1, ());
|
||||
|
|
@ -610,7 +624,7 @@ impl Server {
|
|||
|
||||
#[track_caller]
|
||||
pub fn data_source_mimes(&self) -> Vec<String> {
|
||||
let Some(selection) = &self.state.selection else {
|
||||
let Some(selection) = &self.state.clipboard else {
|
||||
panic!("No selection set on data device");
|
||||
};
|
||||
|
||||
|
|
@ -620,20 +634,28 @@ impl Server {
|
|||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn paste_data(
|
||||
pub fn primary_source_mimes(&self) -> Vec<String> {
|
||||
let Some(selection) = &self.state.primary else {
|
||||
panic!("No selection set on primary device");
|
||||
};
|
||||
|
||||
let data: &Mutex<DataSourceData> = selection.data().unwrap();
|
||||
let data = data.lock().unwrap();
|
||||
data.mimes.to_vec()
|
||||
}
|
||||
|
||||
fn paste_impl(
|
||||
&mut self,
|
||||
mut send_data_for_mime: impl FnMut(&str, &mut Self) -> bool,
|
||||
data: &Mutex<DataSourceData>,
|
||||
mut send_data_for_mime: impl SendDataForMimeFn,
|
||||
mut send_selection: impl FnMut(String, std::os::unix::io::BorrowedFd),
|
||||
) -> Vec<PasteData> {
|
||||
struct PendingData {
|
||||
rx: std::fs::File,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
let Some(selection) = self.state.selection.take() else {
|
||||
panic!("No selection set on data device");
|
||||
};
|
||||
type PendingRet = Vec<(String, Option<PendingData>)>;
|
||||
let mut pending_ret: PendingRet = {
|
||||
let data: &Mutex<DataSourceData> = selection.data().unwrap();
|
||||
data.lock()
|
||||
.unwrap()
|
||||
.mimes
|
||||
|
|
@ -673,7 +695,7 @@ impl Server {
|
|||
Some(pending) => try_transfer(&mut pending_ret, mime, pending),
|
||||
None => {
|
||||
let (rx, tx) = rustix::pipe::pipe().unwrap();
|
||||
selection.send(mime.clone(), tx.as_fd());
|
||||
send_selection(mime.clone(), tx.as_fd());
|
||||
drop(tx);
|
||||
|
||||
let rx = std::fs::File::from(rx);
|
||||
|
|
@ -689,13 +711,47 @@ impl Server {
|
|||
}
|
||||
}
|
||||
|
||||
self.state.selection = Some(selection);
|
||||
ret
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn clipboard_paste_data(
|
||||
&mut self,
|
||||
send_data_for_mime: impl SendDataForMimeFn,
|
||||
) -> Vec<PasteData> {
|
||||
let Some(selection) = self.state.clipboard.take() else {
|
||||
panic!("No selection set on data device");
|
||||
};
|
||||
|
||||
let ret = self.paste_impl(
|
||||
selection.data().unwrap(),
|
||||
send_data_for_mime,
|
||||
|mime_type, fd| selection.send(mime_type, fd),
|
||||
);
|
||||
self.state.clipboard = Some(selection);
|
||||
ret
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn primary_paste_data(
|
||||
&mut self,
|
||||
send_data_for_mime: impl SendDataForMimeFn,
|
||||
) -> Vec<PasteData> {
|
||||
let Some(selection) = self.state.primary.take() else {
|
||||
panic!("No selection set on primary data device");
|
||||
};
|
||||
|
||||
let ret = self.paste_impl(
|
||||
selection.data().unwrap(),
|
||||
send_data_for_mime,
|
||||
|mime_type, fd| selection.send(mime_type, fd),
|
||||
);
|
||||
self.state.primary = Some(selection);
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn data_source_exists(&self) -> bool {
|
||||
self.state.selection.is_none()
|
||||
self.state.clipboard.is_none()
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
|
|
@ -704,7 +760,7 @@ impl Server {
|
|||
panic!("No data device created");
|
||||
};
|
||||
|
||||
if let Some(selection) = self.state.selection.take() {
|
||||
if let Some(selection) = self.state.clipboard.take() {
|
||||
selection.cancelled();
|
||||
}
|
||||
|
||||
|
|
@ -723,13 +779,38 @@ impl Server {
|
|||
self.display.flush_clients().unwrap();
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn create_primary_offer(&mut self, data: Vec<PasteData>) {
|
||||
let Some(dev) = &self.state.primary_device else {
|
||||
panic!("No primary device created");
|
||||
};
|
||||
|
||||
if let Some(selection) = self.state.primary.take() {
|
||||
selection.cancelled();
|
||||
}
|
||||
|
||||
let mimes: Vec<_> = data.iter().map(|m| m.mime_type.clone()).collect();
|
||||
let offer = self
|
||||
.client
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.create_resource::<_, _, State>(&self.dh, 1, data)
|
||||
.unwrap();
|
||||
dev.data_offer(&offer);
|
||||
for mime in mimes {
|
||||
offer.offer(mime);
|
||||
}
|
||||
dev.selection(Some(&offer));
|
||||
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() {
|
||||
if let Some(selection) = self.state.clipboard.take() {
|
||||
selection.cancelled();
|
||||
}
|
||||
|
||||
|
|
@ -996,6 +1077,118 @@ impl Dispatch<WlOutput, ()> for State {
|
|||
}
|
||||
}
|
||||
|
||||
impl GlobalDispatch<ZwpPrimarySelectionDeviceManagerV1, ()> for State {
|
||||
fn bind(
|
||||
state: &mut Self,
|
||||
_: &DisplayHandle,
|
||||
_: &Client,
|
||||
resource: wayland_server::New<ZwpPrimarySelectionDeviceManagerV1>,
|
||||
_: &(),
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
state.primary_man = Some(data_init.init(resource, ()));
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpPrimarySelectionDeviceManagerV1, ()> for State {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &Client,
|
||||
_: &ZwpPrimarySelectionDeviceManagerV1,
|
||||
request: <ZwpPrimarySelectionDeviceManagerV1 as Resource>::Request,
|
||||
_: &(),
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
use wayland_protocols::wp::primary_selection::zv1::server::zwp_primary_selection_device_manager_v1::Request;
|
||||
match request {
|
||||
Request::CreateSource { id } => {
|
||||
data_init.init(id, DataSourceData::default().into());
|
||||
}
|
||||
Request::GetDevice { id, seat } => {
|
||||
state.primary_device = Some(data_init.init(id, seat));
|
||||
}
|
||||
Request::Destroy => {
|
||||
state.primary_man = None;
|
||||
}
|
||||
_ => todo!("{request:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpPrimarySelectionOfferV1, Vec<PasteData>> for State {
|
||||
fn request(
|
||||
_: &mut Self,
|
||||
_: &Client,
|
||||
_: &ZwpPrimarySelectionOfferV1,
|
||||
request: <ZwpPrimarySelectionOfferV1 as Resource>::Request,
|
||||
data: &Vec<PasteData>,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
use wayland_protocols::wp::primary_selection::zv1::server::zwp_primary_selection_offer_v1::Request;
|
||||
match request {
|
||||
Request::Receive { mime_type, fd } => {
|
||||
let pos = data
|
||||
.iter()
|
||||
.position(|data| data.mime_type == mime_type)
|
||||
.unwrap_or_else(|| panic!("Invalid mime type: {mime_type}"));
|
||||
|
||||
let mut stream = UnixStream::from(fd);
|
||||
stream.write_all(&data[pos].data).unwrap();
|
||||
}
|
||||
Request::Destroy => {}
|
||||
other => todo!("{other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpPrimarySelectionSourceV1, Mutex<DataSourceData>> for State {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &Client,
|
||||
_: &ZwpPrimarySelectionSourceV1,
|
||||
request: <ZwpPrimarySelectionSourceV1 as Resource>::Request,
|
||||
data: &Mutex<DataSourceData>,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
use wayland_protocols::wp::primary_selection::zv1::server::zwp_primary_selection_source_v1::Request;
|
||||
match request {
|
||||
Request::Offer { mime_type } => {
|
||||
data.lock().unwrap().mimes.push(mime_type);
|
||||
}
|
||||
Request::Destroy => {
|
||||
state.primary = None;
|
||||
}
|
||||
_ => todo!("{request:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpPrimarySelectionDeviceV1, WlSeat> for State {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &Client,
|
||||
_: &ZwpPrimarySelectionDeviceV1,
|
||||
request: <ZwpPrimarySelectionDeviceV1 as Resource>::Request,
|
||||
_: &WlSeat,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
use wayland_protocols::wp::primary_selection::zv1::server::zwp_primary_selection_device_v1::Request;
|
||||
match request {
|
||||
Request::SetSelection { source, .. } => {
|
||||
state.primary = source;
|
||||
}
|
||||
Request::Destroy => {
|
||||
state.primary_device = None;
|
||||
}
|
||||
other => todo!("unhandled request {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalDispatch<WlDataDeviceManager, ()> for State {
|
||||
fn bind(
|
||||
state: &mut Self,
|
||||
|
|
@ -1051,7 +1244,7 @@ impl Dispatch<WlDataSource, Mutex<DataSourceData>> for State {
|
|||
data.mimes.push(mime_type);
|
||||
}
|
||||
wl_data_source::Request::Destroy => {
|
||||
state.selection = None;
|
||||
state.clipboard = None;
|
||||
}
|
||||
other => todo!("unhandled request {other:?}"),
|
||||
}
|
||||
|
|
@ -1070,7 +1263,7 @@ impl Dispatch<WlDataDevice, WlSeat> for State {
|
|||
) {
|
||||
match request {
|
||||
wl_data_device::Request::SetSelection { source, .. } => {
|
||||
state.selection = source;
|
||||
state.clipboard = source;
|
||||
}
|
||||
wl_data_device::Request::Release => {
|
||||
state.data_device = None;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue