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 crate::xstate::{RealConnection, XState};
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use rustix::event::{poll, PollFd, PollFlags};
|
use rustix::event::{poll, PollFd, PollFlags};
|
||||||
|
use server::selection::{Clipboard, Primary};
|
||||||
use smithay_client_toolkit::data_device_manager::WritePipe;
|
use smithay_client_toolkit::data_device_manager::WritePipe;
|
||||||
use std::io::{BufRead, BufReader, Read, Write};
|
use std::io::{BufRead, BufReader, Read, Write};
|
||||||
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd};
|
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd};
|
||||||
|
|
@ -212,10 +213,14 @@ pub fn main(mut data: impl RunData) -> Option<()> {
|
||||||
server_state.run();
|
server_state.run();
|
||||||
display.flush_clients().unwrap();
|
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);
|
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() {
|
if let Some(scale) = server_state.new_global_scale() {
|
||||||
xstate.update_global_scale(scale);
|
xstate.update_global_scale(scale);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,12 @@ use smithay_client_toolkit::{
|
||||||
data_offer::{DataOfferHandler, SelectionOffer},
|
data_offer::{DataOfferHandler, SelectionOffer},
|
||||||
data_source::DataSourceHandler,
|
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 std::sync::{mpsc, Mutex, OnceLock};
|
||||||
use wayland_client::protocol::{
|
use wayland_client::protocol::{
|
||||||
|
|
@ -41,6 +46,11 @@ use wayland_protocols::{
|
||||||
zwp_locked_pointer_v1::ZwpLockedPointerV1,
|
zwp_locked_pointer_v1::ZwpLockedPointerV1,
|
||||||
zwp_pointer_constraints_v1::ZwpPointerConstraintsV1,
|
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::{
|
tablet::zv2::client::{
|
||||||
zwp_tablet_manager_v2::ZwpTabletManagerV2,
|
zwp_tablet_manager_v2::ZwpTabletManagerV2,
|
||||||
zwp_tablet_pad_group_v2::{ZwpTabletPadGroupV2, EVT_RING_OPCODE, EVT_STRIP_OPCODE},
|
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 wl_drm::client::wl_drm::WlDrm;
|
||||||
use xcb::x;
|
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(super) struct MyWorld {
|
||||||
pub world: World,
|
pub world: World,
|
||||||
pub global_list: GlobalList,
|
pub global_list: GlobalList,
|
||||||
pub new_globals: Vec<Global>,
|
pub new_globals: Vec<Global>,
|
||||||
events: Vec<(Entity, ObjectEvent)>,
|
events: Vec<(Entity, ObjectEvent)>,
|
||||||
queued_events: Vec<mpsc::Receiver<(Entity, ObjectEvent)>>,
|
queued_events: Vec<mpsc::Receiver<(Entity, ObjectEvent)>>,
|
||||||
pub selection_offer: Option<SelectionOffer>,
|
pub clipboard: SelectionEvents<SelectionOffer>,
|
||||||
pub selection_requests: Vec<(
|
pub primary: SelectionEvents<PrimarySelectionOffer>,
|
||||||
String,
|
|
||||||
smithay_client_toolkit::data_device_manager::WritePipe,
|
|
||||||
)>,
|
|
||||||
pub selection_cancelled: bool,
|
|
||||||
pub pending_activations: Vec<(xcb::x::Window, String)>,
|
pub pending_activations: Vec<(xcb::x::Window, String)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -96,9 +121,8 @@ impl MyWorld {
|
||||||
new_globals: Vec::new(),
|
new_globals: Vec::new(),
|
||||||
events: Vec::new(),
|
events: Vec::new(),
|
||||||
queued_events: Vec::new(),
|
queued_events: Vec::new(),
|
||||||
selection_offer: None,
|
clipboard: Default::default(),
|
||||||
selection_requests: Vec::new(),
|
primary: Default::default(),
|
||||||
selection_cancelled: false,
|
|
||||||
pending_activations: Vec::new(),
|
pending_activations: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -156,6 +180,7 @@ delegate_noop!(MyWorld: XdgActivationV1);
|
||||||
delegate_noop!(MyWorld: ZxdgDecorationManagerV1);
|
delegate_noop!(MyWorld: ZxdgDecorationManagerV1);
|
||||||
delegate_noop!(MyWorld: WpFractionalScaleManagerV1);
|
delegate_noop!(MyWorld: WpFractionalScaleManagerV1);
|
||||||
delegate_noop!(MyWorld: ignore ZxdgToplevelDecorationV1);
|
delegate_noop!(MyWorld: ignore ZxdgToplevelDecorationV1);
|
||||||
|
delegate_noop!(MyWorld: ZwpPrimarySelectionDeviceManagerV1);
|
||||||
|
|
||||||
impl Dispatch<WlRegistry, GlobalListContents> for MyWorld {
|
impl Dispatch<WlRegistry, GlobalListContents> for MyWorld {
|
||||||
fn event(
|
fn event(
|
||||||
|
|
@ -387,7 +412,7 @@ impl DataDeviceHandler for MyWorld {
|
||||||
data_device: &wayland_client::protocol::wl_data_device::WlDataDevice,
|
data_device: &wayland_client::protocol::wl_data_device::WlDataDevice,
|
||||||
) {
|
) {
|
||||||
let data: &DataDeviceData = data_device.data().unwrap();
|
let data: &DataDeviceData = data_device.data().unwrap();
|
||||||
self.selection_offer = data.selection_offer();
|
self.clipboard.offer = data.selection_offer();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drop_performed(
|
fn drop_performed(
|
||||||
|
|
@ -437,7 +462,7 @@ impl DataSourceHandler for MyWorld {
|
||||||
mime: String,
|
mime: String,
|
||||||
fd: smithay_client_toolkit::data_device_manager::WritePipe,
|
fd: smithay_client_toolkit::data_device_manager::WritePipe,
|
||||||
) {
|
) {
|
||||||
self.selection_requests.push((mime, fd));
|
self.clipboard.requests.push((mime, fd));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cancelled(
|
fn cancelled(
|
||||||
|
|
@ -446,7 +471,7 @@ impl DataSourceHandler for MyWorld {
|
||||||
_: &wayland_client::QueueHandle<Self>,
|
_: &wayland_client::QueueHandle<Self>,
|
||||||
_: &wayland_client::protocol::wl_data_source::WlDataSource,
|
_: &wayland_client::protocol::wl_data_source::WlDataSource,
|
||||||
) {
|
) {
|
||||||
self.selection_cancelled = true;
|
self.clipboard.cancelled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action(
|
fn action(
|
||||||
|
|
@ -538,3 +563,42 @@ impl ActivationHandler for MyWorld {
|
||||||
self.pending_activations.push((data.window, token));
|
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
|
.global_list
|
||||||
.registry()
|
.registry()
|
||||||
.bind::<client::wl_seat::WlSeat, _, _>(data.name, server.version(), &state.qh, entity);
|
.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));
|
state.world.spawn_at(entity, (server, client));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ use wayland_protocols::{
|
||||||
zwp_tablet_v2::ZwpTabletV2 as TabletServer,
|
zwp_tablet_v2::ZwpTabletV2 as TabletServer,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
viewporter::client::wp_viewport::WpViewport,
|
||||||
},
|
},
|
||||||
xdg::{
|
xdg::{
|
||||||
shell::client::{xdg_popup, xdg_surface, xdg_toplevel},
|
shell::client::{xdg_popup, xdg_surface, xdg_toplevel},
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
mod clientside;
|
mod clientside;
|
||||||
mod dispatch;
|
mod dispatch;
|
||||||
mod event;
|
mod event;
|
||||||
|
pub(crate) mod selection;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
|
@ -12,16 +13,10 @@ use hecs::{Entity, World};
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
use rustix::event::{poll, PollFd, PollFlags};
|
use rustix::event::{poll, PollFd, PollFlags};
|
||||||
use smithay_client_toolkit::activation::ActivationState;
|
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::collections::{HashMap, HashSet};
|
||||||
use std::io::Read;
|
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::os::fd::{AsFd, BorrowedFd};
|
use std::os::fd::{AsFd, BorrowedFd};
|
||||||
use std::os::unix::net::UnixStream;
|
use std::os::unix::net::UnixStream;
|
||||||
use std::rc::{Rc, Weak};
|
|
||||||
use wayland_client::{
|
use wayland_client::{
|
||||||
globals::{registry_queue_init, Global},
|
globals::{registry_queue_init, Global},
|
||||||
protocol as client, Connection, EventQueue, Proxy, QueueHandle,
|
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,
|
zwp_tablet_pad_v2, zwp_tablet_seat_v2, zwp_tablet_tool_v2, zwp_tablet_v2,
|
||||||
},
|
},
|
||||||
tablet::zv2::server::zwp_tablet_manager_v2::ZwpTabletManagerV2,
|
tablet::zv2::server::zwp_tablet_manager_v2::ZwpTabletManagerV2,
|
||||||
viewporter::client::{wp_viewport::WpViewport, wp_viewporter::WpViewporter},
|
viewporter::client::wp_viewporter::WpViewporter,
|
||||||
},
|
},
|
||||||
xdg::{
|
xdg::{
|
||||||
shell::client::{
|
shell::client::{
|
||||||
|
|
@ -454,7 +449,7 @@ pub struct InnerServerState<S: X11Selection> {
|
||||||
viewporter: WpViewporter,
|
viewporter: WpViewporter,
|
||||||
fractional_scale: Option<WpFractionalScaleManagerV1>,
|
fractional_scale: Option<WpFractionalScaleManagerV1>,
|
||||||
decoration_manager: Option<ZxdgDecorationManagerV1>,
|
decoration_manager: Option<ZxdgDecorationManagerV1>,
|
||||||
clipboard_data: Option<ClipboardData<S>>,
|
selection_states: selection::SelectionStates<S>,
|
||||||
last_kb_serial: Option<(client::wl_seat::WlSeat, u32)>,
|
last_kb_serial: Option<(client::wl_seat::WlSeat, u32)>,
|
||||||
activation_state: Option<ActivationState>,
|
activation_state: Option<ActivationState>,
|
||||||
global_output_offset: GlobalOutputOffset,
|
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."))
|
.inspect_err(|e| warn!("Couldn't bind fractional scale manager: {e}. Fractional scaling will not work."))
|
||||||
.ok();
|
.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)
|
let activation_state = ActivationState::bind(&global_list, &qh)
|
||||||
.inspect_err(|e| {
|
.inspect_err(|e| {
|
||||||
warn!("Could not bind xdg activation ({e:?}). Windows might not receive focus depending on compositor focus stealing policy.")
|
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();
|
.ok();
|
||||||
|
|
||||||
|
let selection_states = selection::SelectionStates::new(&global_list, &qh);
|
||||||
|
|
||||||
dh.create_global::<InnerServerState<S>, XwaylandShellV1, _>(1, ());
|
dh.create_global::<InnerServerState<S>, XwaylandShellV1, _>(1, ());
|
||||||
|
|
||||||
global_list
|
global_list
|
||||||
.contents()
|
.contents()
|
||||||
.with_list(|globals| handle_globals::<S>(&dh, globals));
|
.with_list(|globals| handle_globals::<S>(&dh, globals));
|
||||||
|
|
@ -539,7 +526,7 @@ impl<S: X11Selection> ServerState<NoConnection<S>> {
|
||||||
xdg_wm_base,
|
xdg_wm_base,
|
||||||
viewporter,
|
viewporter,
|
||||||
fractional_scale,
|
fractional_scale,
|
||||||
clipboard_data,
|
selection_states,
|
||||||
last_kb_serial: None,
|
last_kb_serial: None,
|
||||||
activation_state,
|
activation_state,
|
||||||
global_output_offset: GlobalOutputOffset {
|
global_output_offset: GlobalOutputOffset {
|
||||||
|
|
@ -691,7 +678,7 @@ impl<C: XConnection> ServerState<C> {
|
||||||
self.unfocus = false;
|
self.unfocus = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.handle_clipboard_events();
|
self.handle_selection_events();
|
||||||
self.handle_activations();
|
self.handle_activations();
|
||||||
self.queue
|
self.queue
|
||||||
.flush()
|
.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> {
|
pub fn new_global_scale(&mut self) -> Option<f64> {
|
||||||
self.new_scale.take()
|
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) {
|
fn handle_activations(&mut self) {
|
||||||
let Some(activation_state) = self.activation_state.as_ref() else {
|
let Some(activation_state) = self.activation_state.as_ref() else {
|
||||||
return;
|
return;
|
||||||
|
|
@ -1444,42 +1363,3 @@ pub struct PendingSurfaceState {
|
||||||
pub width: i32,
|
pub width: i32,
|
||||||
pub height: 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::xstate::{SetState, WinSize, WmName};
|
||||||
use crate::XConnection;
|
use crate::XConnection;
|
||||||
use rustix::event::{poll, PollFd, PollFlags};
|
use rustix::event::{poll, PollFd, PollFlags};
|
||||||
|
|
@ -7,6 +8,7 @@ use std::io::Write;
|
||||||
use std::os::fd::{AsRawFd, BorrowedFd};
|
use std::os::fd::{AsRawFd, BorrowedFd};
|
||||||
use std::os::unix::net::UnixStream;
|
use std::os::unix::net::UnixStream;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
use testwl::SendDataForMimeFn;
|
||||||
use wayland_client::{
|
use wayland_client::{
|
||||||
backend::{protocol::Message, Backend, ObjectData, ObjectId, WaylandError},
|
backend::{protocol::Message, Backend, ObjectData, ObjectId, WaylandError},
|
||||||
protocol::{
|
protocol::{
|
||||||
|
|
@ -1267,8 +1269,70 @@ fn window_group_properties() {
|
||||||
assert_eq!(data.toplevel().app_id, Some("class".into()));
|
assert_eq!(data.toplevel().app_id, Some("class".into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
trait SelectionTest {
|
||||||
fn copy_from_x11() {
|
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 (mut f, comp) = TestFixture::new_with_compositor();
|
||||||
let win = unsafe { Window::new(1) };
|
let win = unsafe { Window::new(1) };
|
||||||
let (_surface, _id) = f.create_toplevel(&comp, win);
|
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();
|
f.run();
|
||||||
|
|
||||||
let server_mimes = f.testwl.data_source_mimes();
|
let server_mimes = T::mimes(&mut f.testwl);
|
||||||
for mime in mimes.iter() {
|
for mime in mimes.iter() {
|
||||||
assert!(server_mimes.contains(&mime.mime_type));
|
assert!(server_mimes.contains(&mime.mime_type));
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = f.testwl.paste_data(|_, _| {
|
let data = T::paste_data(&mut f.testwl, |_, _| {
|
||||||
f.satellite.run();
|
f.satellite.run();
|
||||||
true
|
true
|
||||||
});
|
});
|
||||||
assert_eq!(*mimes, data);
|
assert_eq!(*mimes, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
fn copy_from_wayland<T: SelectionTest>() {
|
||||||
fn copy_from_wayland() {
|
|
||||||
let (mut f, comp) = TestFixture::new_with_compositor();
|
let (mut f, comp) = TestFixture::new_with_compositor();
|
||||||
TestObject::<WlKeyboard>::from_request(&comp.seat.obj, wl_seat::Request::GetKeyboard {});
|
TestObject::<WlKeyboard>::from_request(&comp.seat.obj, wl_seat::Request::GetKeyboard {});
|
||||||
let win = unsafe { Window::new(1) };
|
let win = unsafe { Window::new(1) };
|
||||||
|
|
@ -1316,10 +1379,13 @@ fn copy_from_wayland() {
|
||||||
data: vec![1, 2, 3, 4, 6, 10],
|
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();
|
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 {
|
for mime in &mimes {
|
||||||
let data = std::thread::scope(|s| {
|
let data = std::thread::scope(|s| {
|
||||||
// receive requires a queue flush - dispatch testwl from another thread
|
// receive requires a queue flush - dispatch testwl from another thread
|
||||||
|
|
@ -1341,8 +1407,7 @@ fn copy_from_wayland() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
fn selection_x11_then_wayland<T: SelectionTest>() {
|
||||||
fn clipboard_x11_then_wayland() {
|
|
||||||
let (mut f, comp) = TestFixture::new_with_compositor();
|
let (mut f, comp) = TestFixture::new_with_compositor();
|
||||||
TestObject::<WlKeyboard>::from_request(&comp.seat.obj, wl_seat::Request::GetKeyboard {});
|
TestObject::<WlKeyboard>::from_request(&comp.seat.obj, wl_seat::Request::GetKeyboard {});
|
||||||
let win = unsafe { Window::new(1) };
|
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();
|
f.run();
|
||||||
|
|
||||||
let waylanddata = vec![
|
let waylanddata = vec![
|
||||||
|
|
@ -1372,11 +1438,14 @@ fn clipboard_x11_then_wayland() {
|
||||||
data: vec![10, 20, 40, 50],
|
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();
|
||||||
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 {
|
for mime in &waylanddata {
|
||||||
let data = std::thread::scope(|s| {
|
let data = std::thread::scope(|s| {
|
||||||
// receive requires a queue flush - dispatch testwl from another thread
|
// 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.testwl.empty_data_offer();
|
||||||
f.run();
|
f.run();
|
||||||
|
|
||||||
let selection = f.satellite.new_selection();
|
let selection = f.satellite.new_selection::<Clipboard>();
|
||||||
assert!(selection.is_none());
|
assert!(selection.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
mod settings;
|
mod settings;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
mod selection;
|
mod selection;
|
||||||
use selection::{Selection, SelectionData};
|
use selection::{Selection, SelectionState};
|
||||||
use wayland_protocols::xdg::decoration::zv1::client::zxdg_toplevel_decoration_v1;
|
use wayland_protocols::xdg::decoration::zv1::client::zxdg_toplevel_decoration_v1;
|
||||||
|
|
||||||
use crate::XConnection;
|
use crate::XConnection;
|
||||||
|
|
@ -117,7 +117,7 @@ pub struct XState {
|
||||||
window_atoms: WindowTypes,
|
window_atoms: WindowTypes,
|
||||||
root: x::Window,
|
root: x::Window,
|
||||||
wm_window: x::Window,
|
wm_window: x::Window,
|
||||||
selection_data: SelectionData,
|
selection_state: SelectionState,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -201,6 +201,15 @@ impl XState {
|
||||||
| SelectionEventMask::SELECTION_CLIENT_CLOSE,
|
| SelectionEventMask::SELECTION_CLIENT_CLOSE,
|
||||||
})
|
})
|
||||||
.unwrap();
|
.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
|
// Setup default cursor theme
|
||||||
let ctx = CursorContext::new(&connection, screen).unwrap();
|
let ctx = CursorContext::new(&connection, screen).unwrap();
|
||||||
|
|
@ -214,7 +223,7 @@ impl XState {
|
||||||
}
|
}
|
||||||
|
|
||||||
let wm_window = connection.generate_id();
|
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 window_atoms = WindowTypes::intern_all(&connection).unwrap();
|
||||||
let settings = Settings::new(&connection, &atoms, root);
|
let settings = Settings::new(&connection, &atoms, root);
|
||||||
|
|
||||||
|
|
@ -224,7 +233,7 @@ impl XState {
|
||||||
root,
|
root,
|
||||||
atoms,
|
atoms,
|
||||||
window_atoms,
|
window_atoms,
|
||||||
selection_data,
|
selection_state,
|
||||||
settings,
|
settings,
|
||||||
};
|
};
|
||||||
r.create_ewmh_window();
|
r.create_ewmh_window();
|
||||||
|
|
@ -917,6 +926,7 @@ xcb::atoms_struct! {
|
||||||
incr => b"INCR" only_if_exists = false,
|
incr => b"INCR" only_if_exists = false,
|
||||||
xsettings => b"_XSETTINGS_S0" only_if_exists = false,
|
xsettings => b"_XSETTINGS_S0" only_if_exists = false,
|
||||||
xsettings_settings => b"_XSETTINGS_SETTINGS" 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 super::{get_atom_name, XState};
|
||||||
use crate::server::ForeignSelection;
|
use crate::server::selection::{Clipboard, ForeignSelection, Primary, SelectionType};
|
||||||
use crate::{RealServerState, X11Selection};
|
use crate::{RealServerState, X11Selection};
|
||||||
use log::{debug, error, warn};
|
use log::{debug, error, warn};
|
||||||
use smithay_client_toolkit::data_device_manager::WritePipe;
|
use smithay_client_toolkit::data_device_manager::WritePipe;
|
||||||
|
|
@ -26,7 +26,7 @@ pub struct Selection {
|
||||||
connection: Rc<xcb::Connection>,
|
connection: Rc<xcb::Connection>,
|
||||||
window: x::Window,
|
window: x::Window,
|
||||||
pending: RefCell<Vec<PendingSelectionData>>,
|
pending: RefCell<Vec<PendingSelectionData>>,
|
||||||
clipboard: x::Atom,
|
selection: x::Atom,
|
||||||
selection_time: u32,
|
selection_time: u32,
|
||||||
incr: x::Atom,
|
incr: x::Atom,
|
||||||
}
|
}
|
||||||
|
|
@ -46,13 +46,13 @@ impl X11Selection for Selection {
|
||||||
.connection
|
.connection
|
||||||
.send_and_check_request(&x::ConvertSelection {
|
.send_and_check_request(&x::ConvertSelection {
|
||||||
requestor: self.window,
|
requestor: self.window,
|
||||||
selection: self.clipboard,
|
selection: self.selection,
|
||||||
target: target.atom,
|
target: target.atom,
|
||||||
property: target.atom,
|
property: target.atom,
|
||||||
time: self.selection_time,
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -162,21 +162,275 @@ impl Selection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CurrentSelection {
|
enum CurrentSelection<T: SelectionType> {
|
||||||
X11(Rc<Selection>),
|
X11(Rc<Selection>),
|
||||||
Wayland {
|
Wayland {
|
||||||
mimes: Vec<SelectionTargetId>,
|
mimes: Vec<SelectionTargetId>,
|
||||||
inner: ForeignSelection,
|
inner: ForeignSelection<T>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
pub(crate) struct SelectionData {
|
|
||||||
|
struct SelectionData<T: SelectionType> {
|
||||||
last_selection_timestamp: u32,
|
last_selection_timestamp: u32,
|
||||||
target_window: x::Window,
|
atom: x::Atom,
|
||||||
current_selection: Option<CurrentSelection>,
|
current_selection: Option<CurrentSelection<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SelectionData {
|
// This is a trait so that we can use &dyn
|
||||||
pub fn new(connection: &xcb::Connection, root: x::Window) -> Self {
|
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();
|
let target_window = connection.generate_id();
|
||||||
connection
|
connection
|
||||||
.send_and_check_request(&x::CreateWindow {
|
.send_and_check_request(&x::CreateWindow {
|
||||||
|
|
@ -195,39 +449,15 @@ impl SelectionData {
|
||||||
})
|
})
|
||||||
.expect("Couldn't create window for selections");
|
.expect("Couldn't create window for selections");
|
||||||
Self {
|
Self {
|
||||||
last_selection_timestamp: x::CURRENT_TIME,
|
|
||||||
target_window,
|
target_window,
|
||||||
current_selection: None,
|
clipboard: SelectionData::new(atoms.clipboard),
|
||||||
|
primary: SelectionData::new(atoms.primary),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl XState {
|
impl XState {
|
||||||
fn set_clipboard_owner(&mut self) {
|
pub(crate) fn set_clipboard(&mut self, selection: ForeignSelection<Clipboard>) {
|
||||||
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) {
|
|
||||||
let mut utf8_xwl = false;
|
let mut utf8_xwl = false;
|
||||||
let mut utf8_wl = false;
|
let mut utf8_wl = false;
|
||||||
let mut mimes: Vec<SelectionTargetId> = selection
|
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,
|
mimes,
|
||||||
inner: selection,
|
inner: selection,
|
||||||
});
|
});
|
||||||
self.set_clipboard_owner();
|
self.selection_state
|
||||||
|
.clipboard
|
||||||
|
.set_owner(&self.connection, self.wm_window);
|
||||||
debug!("Clipboard set from Wayland");
|
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(
|
pub(super) fn handle_selection_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: &xcb::Event,
|
event: &xcb::Event,
|
||||||
server_state: &mut RealServerState,
|
server_state: &mut RealServerState,
|
||||||
) -> bool {
|
) -> 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 {
|
match event {
|
||||||
xcb::Event::X(x::Event::SelectionClear(e)) => {
|
xcb::Event::X(x::Event::SelectionClear(e)) => {
|
||||||
if e.selection() == self.atoms.clipboard {
|
let data = get_selection_data!(e.selection());
|
||||||
self.handle_new_selection_owner(e.owner(), e.time());
|
data.handle_new_owner(
|
||||||
}
|
&self.connection,
|
||||||
|
self.wm_window,
|
||||||
|
&self.atoms,
|
||||||
|
e.owner(),
|
||||||
|
e.time(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
xcb::Event::X(x::Event::SelectionNotify(e)) => {
|
xcb::Event::X(x::Event::SelectionNotify(e)) => {
|
||||||
if e.property() == x::ATOM_NONE {
|
if e.property() == x::ATOM_NONE {
|
||||||
warn!("selection notify fail?");
|
warn!(
|
||||||
|
"selection notify fail? {}",
|
||||||
|
get_atom_name(&self.connection, e.selection())
|
||||||
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let data = get_selection_data!(e.selection());
|
||||||
debug!(
|
debug!(
|
||||||
"selection notify requestor: {:?} target: {}",
|
"selection notify requestor: {:?} target: {} selection: {}",
|
||||||
e.requestor(),
|
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 {
|
if e.requestor() == self.wm_window {
|
||||||
match e.target() {
|
match e.target() {
|
||||||
x if x == self.atoms.targets => {
|
x if x == self.atoms.targets => data.handle_target_list(
|
||||||
self.handle_target_list(e.property(), server_state)
|
&self.connection,
|
||||||
}
|
self.wm_window,
|
||||||
|
&self.atoms,
|
||||||
|
self.selection_state.target_window,
|
||||||
|
e.property(),
|
||||||
|
server_state,
|
||||||
|
),
|
||||||
other => warn!(
|
other => warn!(
|
||||||
"got unexpected selection notify for target {}",
|
"got unexpected selection notify for target {}",
|
||||||
get_atom_name(&self.connection, other)
|
get_atom_name(&self.connection, other)
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
} else if e.requestor() == self.selection_data.target_window {
|
} else if e.requestor() == self.selection_state.target_window {
|
||||||
if let Some(CurrentSelection::X11(selection)) =
|
if let Some(selection) = data.x11_selection() {
|
||||||
&self.selection_data.current_selection
|
|
||||||
{
|
|
||||||
selection.handle_notify(e.target());
|
selection.handle_notify(e.target());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -328,6 +640,7 @@ impl XState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
xcb::Event::X(x::Event::SelectionRequest(e)) => {
|
xcb::Event::X(x::Event::SelectionRequest(e)) => {
|
||||||
|
let data = get_selection_data!(e.selection());
|
||||||
let send_notify = |property| {
|
let send_notify = |property| {
|
||||||
self.connection
|
self.connection
|
||||||
.send_and_check_request(&x::SendEvent {
|
.send_and_check_request(&x::SendEvent {
|
||||||
|
|
@ -349,7 +662,8 @@ impl XState {
|
||||||
|
|
||||||
if log::log_enabled!(log::Level::Debug) {
|
if log::log_enabled!(log::Level::Debug) {
|
||||||
let target = get_atom_name(&self.connection, e.target());
|
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 {
|
if e.property() == x::ATOM_NONE {
|
||||||
|
|
@ -358,76 +672,37 @@ impl XState {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(CurrentSelection::Wayland { mimes, inner }) =
|
data.handle_selection_request(
|
||||||
&self.selection_data.current_selection
|
&self.connection,
|
||||||
else {
|
&self.atoms,
|
||||||
warn!("Got selection request, but we don't seem to be the selection owner");
|
e,
|
||||||
refuse();
|
&success,
|
||||||
return true;
|
&refuse,
|
||||||
};
|
server_state,
|
||||||
|
);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
xcb::Event::XFixes(xcb::xfixes::Event::SelectionNotify(e)) => match e.selection() {
|
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 => {
|
xcb::xfixes::SelectionEvent::SetSelectionOwner => {
|
||||||
if e.owner() == self.wm_window {
|
if e.owner() == self.wm_window {
|
||||||
return true;
|
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::SelectionClientClose
|
||||||
| xcb::xfixes::SelectionEvent::SelectionWindowDestroy => {
|
| xcb::xfixes::SelectionEvent::SelectionWindowDestroy => {
|
||||||
debug!("Selection owner destroyed, selection will be unset");
|
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() {
|
x if x == self.atoms.xsettings => match e.subtype() {
|
||||||
|
|
@ -446,106 +721,18 @@ impl XState {
|
||||||
true
|
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(
|
pub(super) fn handle_selection_property_change(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: &x::PropertyNotifyEvent,
|
event: &x::PropertyNotifyEvent,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if let Some(CurrentSelection::X11(selection)) = &self.selection_data.current_selection {
|
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);
|
return selection.check_for_incr(event);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
false
|
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 request = connection.await_selection_request();
|
||||||
let data = mimes_truth
|
let data = mimes_truth
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -1201,7 +1201,7 @@ fn bad_clipboard_data() {
|
||||||
connection.send_selection_notify(&request);
|
connection.send_selection_notify(&request);
|
||||||
|
|
||||||
f.wait_and_dispatch();
|
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();
|
let request = connection.await_selection_request();
|
||||||
assert_eq!(request.target(), connection.atoms.mime2);
|
assert_eq!(request.target(), connection.atoms.mime2);
|
||||||
// Don't actually set any data as requested - just report success
|
// Don't actually set any data as requested - just report success
|
||||||
|
|
@ -1388,7 +1388,7 @@ fn incr_copy_from_x11() {
|
||||||
.take(3000)
|
.take(3000)
|
||||||
.collect();
|
.collect();
|
||||||
let mut it = data.chunks(500).enumerate();
|
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() {
|
if let Some(begin) = begin_incr.take() {
|
||||||
destination_property = begin(&mut connection);
|
destination_property = begin(&mut connection);
|
||||||
testwl.dispatch();
|
testwl.dispatch();
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,10 @@ use std::os::fd::{AsFd, BorrowedFd, OwnedFd};
|
||||||
use std::os::unix::net::UnixStream;
|
use std::os::unix::net::UnixStream;
|
||||||
use std::sync::{Arc, Mutex, OnceLock};
|
use std::sync::{Arc, Mutex, OnceLock};
|
||||||
use std::time::Instant;
|
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::{
|
use wayland_protocols::{
|
||||||
wp::{
|
wp::{
|
||||||
fractional_scale::v1::server::{
|
fractional_scale::v1::server::{
|
||||||
|
|
@ -253,9 +257,12 @@ struct State {
|
||||||
tablet: Option<ZwpTabletV2>,
|
tablet: Option<ZwpTabletV2>,
|
||||||
tablet_tool: Option<ZwpTabletToolV2>,
|
tablet_tool: Option<ZwpTabletToolV2>,
|
||||||
configure_serial: u32,
|
configure_serial: u32,
|
||||||
selection: Option<WlDataSource>,
|
clipboard: Option<WlDataSource>,
|
||||||
|
primary: Option<ZwpPrimarySelectionSourceV1>,
|
||||||
data_device_man: Option<WlDataDeviceManager>,
|
data_device_man: Option<WlDataDeviceManager>,
|
||||||
data_device: Option<WlDataDevice>,
|
data_device: Option<WlDataDevice>,
|
||||||
|
primary_man: Option<ZwpPrimarySelectionDeviceManagerV1>,
|
||||||
|
primary_device: Option<ZwpPrimarySelectionDeviceV1>,
|
||||||
xdg_activation: Option<XdgActivationV1>,
|
xdg_activation: Option<XdgActivationV1>,
|
||||||
valid_tokens: HashSet<String>,
|
valid_tokens: HashSet<String>,
|
||||||
token_counter: u32,
|
token_counter: u32,
|
||||||
|
|
@ -279,7 +286,10 @@ impl Default for State {
|
||||||
tablet: None,
|
tablet: None,
|
||||||
tablet_tool: None,
|
tablet_tool: None,
|
||||||
configure_serial: 0,
|
configure_serial: 0,
|
||||||
selection: None,
|
clipboard: None,
|
||||||
|
primary: None,
|
||||||
|
primary_man: None,
|
||||||
|
primary_device: None,
|
||||||
data_device_man: None,
|
data_device_man: None,
|
||||||
data_device: None,
|
data_device: None,
|
||||||
xdg_activation: None,
|
xdg_activation: None,
|
||||||
|
|
@ -404,6 +414,9 @@ pub struct Server {
|
||||||
client: Option<Client>,
|
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 {
|
impl Server {
|
||||||
pub fn new(noops: bool) -> Self {
|
pub fn new(noops: bool) -> Self {
|
||||||
let display = Display::new().unwrap();
|
let display = Display::new().unwrap();
|
||||||
|
|
@ -436,6 +449,7 @@ impl Server {
|
||||||
dh.create_global::<State, XdgWmBase, _>(6, ());
|
dh.create_global::<State, XdgWmBase, _>(6, ());
|
||||||
dh.create_global::<State, WlSeat, _>(5, ());
|
dh.create_global::<State, WlSeat, _>(5, ());
|
||||||
dh.create_global::<State, WlDataDeviceManager, _>(3, ());
|
dh.create_global::<State, WlDataDeviceManager, _>(3, ());
|
||||||
|
dh.create_global::<State, ZwpPrimarySelectionDeviceManagerV1, _>(1, ());
|
||||||
dh.create_global::<State, ZwpTabletManagerV2, _>(1, ());
|
dh.create_global::<State, ZwpTabletManagerV2, _>(1, ());
|
||||||
dh.create_global::<State, XdgActivationV1, _>(1, ());
|
dh.create_global::<State, XdgActivationV1, _>(1, ());
|
||||||
dh.create_global::<State, ZxdgDecorationManagerV1, _>(1, ());
|
dh.create_global::<State, ZxdgDecorationManagerV1, _>(1, ());
|
||||||
|
|
@ -610,7 +624,7 @@ impl Server {
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn data_source_mimes(&self) -> Vec<String> {
|
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");
|
panic!("No selection set on data device");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -620,20 +634,28 @@ impl Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[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 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> {
|
) -> Vec<PasteData> {
|
||||||
struct PendingData {
|
struct PendingData {
|
||||||
rx: std::fs::File,
|
rx: std::fs::File,
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
}
|
}
|
||||||
let Some(selection) = self.state.selection.take() else {
|
|
||||||
panic!("No selection set on data device");
|
|
||||||
};
|
|
||||||
type PendingRet = Vec<(String, Option<PendingData>)>;
|
type PendingRet = Vec<(String, Option<PendingData>)>;
|
||||||
let mut pending_ret: PendingRet = {
|
let mut pending_ret: PendingRet = {
|
||||||
let data: &Mutex<DataSourceData> = selection.data().unwrap();
|
|
||||||
data.lock()
|
data.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.mimes
|
.mimes
|
||||||
|
|
@ -673,7 +695,7 @@ impl Server {
|
||||||
Some(pending) => try_transfer(&mut pending_ret, mime, pending),
|
Some(pending) => try_transfer(&mut pending_ret, mime, pending),
|
||||||
None => {
|
None => {
|
||||||
let (rx, tx) = rustix::pipe::pipe().unwrap();
|
let (rx, tx) = rustix::pipe::pipe().unwrap();
|
||||||
selection.send(mime.clone(), tx.as_fd());
|
send_selection(mime.clone(), tx.as_fd());
|
||||||
drop(tx);
|
drop(tx);
|
||||||
|
|
||||||
let rx = std::fs::File::from(rx);
|
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
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn data_source_exists(&self) -> bool {
|
pub fn data_source_exists(&self) -> bool {
|
||||||
self.state.selection.is_none()
|
self.state.clipboard.is_none()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
|
|
@ -704,7 +760,7 @@ impl Server {
|
||||||
panic!("No data device created");
|
panic!("No data device created");
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(selection) = self.state.selection.take() {
|
if let Some(selection) = self.state.clipboard.take() {
|
||||||
selection.cancelled();
|
selection.cancelled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -723,13 +779,38 @@ impl Server {
|
||||||
self.display.flush_clients().unwrap();
|
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]
|
#[track_caller]
|
||||||
pub fn empty_data_offer(&mut self) {
|
pub fn empty_data_offer(&mut self) {
|
||||||
let Some(dev) = &self.state.data_device else {
|
let Some(dev) = &self.state.data_device else {
|
||||||
panic!("No data device created");
|
panic!("No data device created");
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(selection) = self.state.selection.take() {
|
if let Some(selection) = self.state.clipboard.take() {
|
||||||
selection.cancelled();
|
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 {
|
impl GlobalDispatch<WlDataDeviceManager, ()> for State {
|
||||||
fn bind(
|
fn bind(
|
||||||
state: &mut Self,
|
state: &mut Self,
|
||||||
|
|
@ -1051,7 +1244,7 @@ impl Dispatch<WlDataSource, Mutex<DataSourceData>> for State {
|
||||||
data.mimes.push(mime_type);
|
data.mimes.push(mime_type);
|
||||||
}
|
}
|
||||||
wl_data_source::Request::Destroy => {
|
wl_data_source::Request::Destroy => {
|
||||||
state.selection = None;
|
state.clipboard = None;
|
||||||
}
|
}
|
||||||
other => todo!("unhandled request {other:?}"),
|
other => todo!("unhandled request {other:?}"),
|
||||||
}
|
}
|
||||||
|
|
@ -1070,7 +1263,7 @@ impl Dispatch<WlDataDevice, WlSeat> for State {
|
||||||
) {
|
) {
|
||||||
match request {
|
match request {
|
||||||
wl_data_device::Request::SetSelection { source, .. } => {
|
wl_data_device::Request::SetSelection { source, .. } => {
|
||||||
state.selection = source;
|
state.clipboard = source;
|
||||||
}
|
}
|
||||||
wl_data_device::Request::Release => {
|
wl_data_device::Request::Release => {
|
||||||
state.data_device = None;
|
state.data_device = None;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue