atagen

monolith

public
Star0
Watch0

Commit 63126b8ac7c9

tarball

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

1 file changed+64-13
xwayland: relay STRING selections through the event loop

latin1_relay parked a thread in read_to_end for as long as the selection owner kept the pipe open, buffered without bound, and thread::spawn panics when the process is out of threads. The pipe is now a nonblocking calloop source that drops a transfer past 16 MiB, and the write to the X client waits for it to be writable.
Modified file: crates/monolith/src/xwayland.rs+64 additions−13 deletions
@@ -9,7 +9,7 @@ use crate::state::State;
99 use monolith_config::SelectionAccess;
1010 use smithay::desktop::Window;
1111 use smithay::reexports::calloop::generic::Generic;
12-use smithay::reexports::calloop::{Interest, Mode, PostAction, RegistrationToken};
12+use smithay::reexports::calloop::{Interest, LoopHandle, Mode, PostAction, RegistrationToken};
1313 use smithay::reexports::rustix::fs::{OFlags, fcntl_setfl};
1414 use smithay::reexports::rustix::io::{FdFlags, fcntl_getfd, fcntl_setfd};
1515 use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
@@ -26,7 +26,8 @@ use smithay::wayland::selection::primary_selection::{
2626 use smithay::wayland::xwayland_shell::{XWaylandShellHandler, XWaylandShellState};
2727 use smithay::xwayland::xwm::{Reorder, ResizeEdge, WmWindowProperty, WmWindowType, XwmId};
2828 use smithay::xwayland::{X11Surface, X11Wm, XWayland, XWaylandEvent, XwmHandler};
29-use std::io::{Read, Write};
29+use std::fs::File;
30+use std::io::{ErrorKind, Read, Write};
3031 use std::os::fd::AsFd;
3132 use std::os::fd::OwnedFd;
3233 use std::os::linux::net::SocketAddrExt;
@@ -40,6 +41,10 @@ const UTF8_TEXT: [&str; 4] = [
4041 "text/plain",
4142 ];
4243
44+/// The most text a STRING request relays, so a source that never stops cannot
45+/// grow the compositor without bound.
46+const RELAY_CAP: usize = 16 << 20;
47+
4348 fn utf8_source(mimes: &[String]) -> Option<&str> {
4449 UTF8_TEXT.into_iter().find(|t| mimes.iter().any(|m| m == t))
4550 }
@@ -61,19 +66,65 @@ fn latin1(text: &[u8]) -> Vec<u8> {
6166 }
6267
6368 /// Hands back the write end of a pipe whose UTF-8 contents reach `out` as
64-/// Latin-1, which is what ICCCM says STRING holds.
65-fn latin1_relay(out: OwnedFd) -> std::io::Result<OwnedFd> {
66- let (mut read, write) = std::io::pipe()?;
67- fcntl_setfl(&out, OFlags::empty())?;
68- std::thread::spawn(move || {
69- let mut text = Vec::new();
70- if read.read_to_end(&mut text).is_ok() {
71- let _ = std::fs::File::from(out).write_all(&latin1(&text));
72- }
73- });
69+/// Latin-1, which is what ICCCM says STRING holds. Text past `RELAY_CAP` is
70+/// dropped along with the transfer.
71+fn latin1_relay(handle: &LoopHandle<'static, State>, out: OwnedFd) -> std::io::Result<OwnedFd> {
72+ let (read, write) = std::io::pipe()?;
73+ fcntl_setfl(&read, OFlags::NONBLOCK)?;
74+ fcntl_setfl(&out, OFlags::NONBLOCK)?;
75+ let mut out = Some(File::from(out));
76+ let mut text = Vec::new();
77+ let next = handle.clone();
78+ handle
79+ .insert_source(
80+ Generic::new(read, Interest::READ, Mode::Level),
81+ move |_, read, _| {
82+ let mut chunk = [0u8; 8192];
83+ loop {
84+ match (&**read).read(&mut chunk) {
85+ Ok(0) => break,
86+ Ok(n) if text.len() + n > RELAY_CAP => return Ok(PostAction::Remove),
87+ Ok(n) => text.extend_from_slice(&chunk[..n]),
88+ Err(e) if e.kind() == ErrorKind::Interrupted => {}
89+ Err(e) if e.kind() == ErrorKind::WouldBlock => {
90+ return Ok(PostAction::Continue);
91+ }
92+ Err(_) => return Ok(PostAction::Remove),
93+ }
94+ }
95+ if let Some(out) = out.take() {
96+ write_when_ready(&next, out, latin1(&text));
97+ }
98+ Ok(PostAction::Remove)
99+ },
100+ )
101+ .map_err(|e| std::io::Error::other(e.to_string()))?;
74102 Ok(write.into())
75103 }
76104
105+fn write_when_ready(handle: &LoopHandle<'static, State>, out: File, data: Vec<u8>) {
106+ let mut sent = 0;
107+ let inserted = handle.insert_source(
108+ Generic::new(out, Interest::WRITE, Mode::Level),
109+ move |_, out, _| {
110+ while sent < data.len() {
111+ match (&**out).write(&data[sent..]) {
112+ Ok(n) => sent += n,
113+ Err(e) if e.kind() == ErrorKind::Interrupted => {}
114+ Err(e) if e.kind() == ErrorKind::WouldBlock => {
115+ return Ok(PostAction::Continue);
116+ }
117+ Err(_) => break,
118+ }
119+ }
120+ Ok(PostAction::Remove)
121+ },
122+ );
123+ if let Err(e) = inserted {
124+ tracing::warn!("cannot relay STRING to X11: {e}");
125+ }
126+}
127+
77128 pub struct Xwayland {
78129 pub display: u32,
79130 listeners: Vec<UnixListener>,
@@ -427,7 +478,7 @@ impl XwmHandler for State {
427478 let mimes = x.wayland_selection(selection).as_deref()?;
428479 utf8_source(mimes).map(str::to_owned)
429480 });
430- let relay = source.map(|s| latin1_relay(fd).map(|w| (s, w)));
481+ let relay = source.map(|s| latin1_relay(&self.core.handle, fd).map(|w| (s, w)));
431482 match relay {
432483 Some(Ok(relay)) => relay,
433484 Some(Err(e)) => {