atagen

monolith

public
Star0
Watch0

Commit 5c3ede313624

tarball

atagen <boss@atagen.co>2026-10-19 07:56 UTC

2 files changed+92-1
xwayland: bridge the X11 and Wayland selections

XwmHandler implemented none of the selection hooks and smithay's default allow_selection_access refuses, so clipboard and primary selection never crossed between X11 and Wayland clients. SelectionHandler now forwards Wayland owners to the X11 selection owner, and X11 owners are exposed through set_data_device_selection and set_primary_selection so data-control clients see them too.
Modified file: crates/monolith/src/seat.rs+30 additions−1 deletions
@@ -6,13 +6,14 @@ use smithay::reexports::wayland_server::Resource;
66 use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
77 use smithay::utils::Serial;
88 use smithay::wayland::seat::WaylandFocus;
9-use smithay::wayland::selection::SelectionHandler;
109 use smithay::wayland::selection::data_device::{
1110 DataDeviceHandler, DataDeviceState, WaylandDndGrabHandler, set_data_device_focus,
1211 };
1312 use smithay::wayland::selection::primary_selection::{
1413 PrimarySelectionHandler, PrimarySelectionState, set_primary_focus,
1514 };
15+use smithay::wayland::selection::{SelectionHandler, SelectionSource, SelectionTarget};
16+use std::os::fd::OwnedFd;
1617
1718 impl SeatHandler for State {
1819 type KeyboardFocus = crate::focus::Focus;
@@ -39,6 +40,34 @@ impl SeatHandler for State {
3940
4041 impl SelectionHandler for State {
4142 type SelectionUserData = ();
43+
44+ fn new_selection(
45+ &mut self,
46+ ty: SelectionTarget,
47+ source: Option<SelectionSource>,
48+ _seat: Seat<Self>,
49+ ) {
50+ if let Some(wm) = self.services.xwayland.as_mut().and_then(|x| x.wm.as_mut())
51+ && let Err(e) = wm.new_selection(ty, source.map(|s| s.mime_types()))
52+ {
53+ tracing::warn!("cannot set the X11 {ty:?} selection: {e:?}");
54+ }
55+ }
56+
57+ fn send_selection(
58+ &mut self,
59+ ty: SelectionTarget,
60+ mime_type: String,
61+ fd: OwnedFd,
62+ _seat: Seat<Self>,
63+ _user_data: &(),
64+ ) {
65+ if let Some(wm) = self.services.xwayland.as_mut().and_then(|x| x.wm.as_mut())
66+ && let Err(e) = wm.send_selection(ty, mime_type, fd)
67+ {
68+ tracing::warn!("cannot read the X11 {ty:?} selection: {e:?}");
69+ }
70+ }
4271 }
4372
4473 impl DataDeviceHandler for State {
Modified file: crates/monolith/src/xwayland.rs+62 additions
@@ -3,6 +3,7 @@
33 //! connection that woke it is served. Xwayland exits once its last client
44 //! leaves and the socket is watched again.
55
6+use crate::focus::Focus;
67 use crate::state::State;
78 use smithay::desktop::Window;
89 use smithay::reexports::calloop::generic::Generic;
@@ -10,10 +11,20 @@ use smithay::reexports::calloop::{Interest, Mode, PostAction, RegistrationToken}
1011 use smithay::reexports::rustix::io::{FdFlags, fcntl_getfd, fcntl_setfd};
1112 use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
1213 use smithay::utils::{Logical, Rectangle, Size};
14+use smithay::wayland::selection::SelectionTarget;
15+use smithay::wayland::selection::data_device::{
16+ clear_data_device_selection, current_data_device_selection_userdata,
17+ request_data_device_client_selection, set_data_device_selection,
18+};
19+use smithay::wayland::selection::primary_selection::{
20+ clear_primary_selection, current_primary_selection_userdata, request_primary_client_selection,
21+ set_primary_selection,
22+};
1323 use smithay::wayland::xwayland_shell::{XWaylandShellHandler, XWaylandShellState};
1424 use smithay::xwayland::xwm::{Reorder, ResizeEdge, WmWindowProperty, WmWindowType, XwmId};
1525 use smithay::xwayland::{X11Surface, X11Wm, XWayland, XWaylandEvent, XwmHandler};
1626 use std::os::fd::AsFd;
27+use std::os::fd::OwnedFd;
1728 use std::os::linux::net::SocketAddrExt;
1829 use std::os::unix::net::{SocketAddr, UnixListener};
1930 use std::process::Stdio;
@@ -327,6 +338,57 @@ impl XwmHandler for State {
327338 }
328339 }
329340
341+ fn allow_selection_access(&mut self, xwm: XwmId, _selection: SelectionTarget) -> bool {
342+ matches!(
343+ self.input.seat.get_keyboard().and_then(|k| k.current_focus()),
344+ Some(Focus::X11(x)) if x.xwm_id() == Some(xwm)
345+ )
346+ }
347+
348+ fn send_selection(
349+ &mut self,
350+ _xwm: XwmId,
351+ selection: SelectionTarget,
352+ mime_type: String,
353+ fd: OwnedFd,
354+ ) {
355+ let seat = &self.input.seat;
356+ let result = match selection {
357+ SelectionTarget::Clipboard => request_data_device_client_selection(seat, mime_type, fd)
358+ .map_err(|e| format!("{e:?}")),
359+ SelectionTarget::Primary => {
360+ request_primary_client_selection(seat, mime_type, fd).map_err(|e| format!("{e:?}"))
361+ }
362+ };
363+ if let Err(e) = result {
364+ tracing::warn!("cannot serve {selection:?} to X11: {e}");
365+ }
366+ }
367+
368+ fn new_selection(&mut self, _xwm: XwmId, selection: SelectionTarget, mime_types: Vec<String>) {
369+ let (dh, seat) = (&self.core.display, &self.input.seat);
370+ match selection {
371+ SelectionTarget::Clipboard => set_data_device_selection(dh, seat, mime_types, ()),
372+ SelectionTarget::Primary => set_primary_selection(dh, seat, mime_types, ()),
373+ }
374+ }
375+
376+ fn cleared_selection(&mut self, _xwm: XwmId, selection: SelectionTarget) {
377+ let (dh, seat) = (&self.core.display, &self.input.seat);
378+ match selection {
379+ SelectionTarget::Clipboard => {
380+ if current_data_device_selection_userdata(seat).is_some() {
381+ clear_data_device_selection(dh, seat);
382+ }
383+ }
384+ SelectionTarget::Primary => {
385+ if current_primary_selection_userdata(seat).is_some() {
386+ clear_primary_selection(dh, seat);
387+ }
388+ }
389+ }
390+ }
391+
330392 fn disconnected(&mut self, _xwm: XwmId) {
331393 tracing::info!("Xwayland exited");
332394 let gone: Vec<_> = self