Commit 706b42a51787
tarball1 file changed+69-11
@@ -9,6 +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::timer::{TimeoutAction, Timer};1213
use smithay::reexports::calloop::{Interest, LoopHandle, Mode, PostAction, RegistrationToken};1314
use smithay::reexports::rustix::fs::{OFlags, fcntl_setfl};1415
use smithay::reexports::rustix::io::{FdFlags, fcntl_getfd, fcntl_setfd};@@ -26,6 +27,7 @@ use smithay::wayland::selection::primary_selection::{
2627
use smithay::wayland::xwayland_shell::{XWaylandShellHandler, XWaylandShellState};2728
use smithay::xwayland::xwm::{Reorder, ResizeEdge, WmWindowProperty, WmWindowType, XwmId};2829
use smithay::xwayland::{X11Surface, X11Wm, XWayland, XWaylandEvent, XwmHandler};30
+use std::cell::Cell;2931
use std::fs::File;3032
use std::io::{ErrorKind, Read, Write};3133
use std::os::fd::AsFd;@@ -33,6 +35,8 @@ use std::os::fd::OwnedFd;
3335
use std::os::linux::net::SocketAddrExt;3436
use std::os::unix::net::{SocketAddr, UnixListener};3537
use std::process::Stdio;38
+use std::rc::Rc;39
+use std::time::{Duration, Instant};3640
3741
const UTF8_TEXT: [&str; 4] = [3842
"text/plain;charset=utf-8",@@ -45,6 +49,9 @@ const UTF8_TEXT: [&str; 4] = [
4549
/// grow the compositor without bound.4650
const RELAY_CAP: usize = 16 << 20;4751
52
+/// How long a STRING relay may go without moving data before it is closed.53
+const RELAY_IDLE: Duration = Duration::from_secs(30);54
+4855
fn utf8_source(mimes: &[String]) -> Option<&str> {4956
UTF8_TEXT.into_iter().find(|t| mimes.iter().any(|m| m == t))5057
}@@ -65,17 +72,29 @@ fn latin1(text: &[u8]) -> Vec<u8> {
6572
.collect()6673
}6774
75
+/// The live source of a STRING relay and when it last moved data, so a timer76
+/// can close one that stalled.77
+struct Relay {78
+ source: Cell<Option<RegistrationToken>>,79
+ moved: Cell<Instant>,80
+}81
+6882
/// Hands back the write end of a pipe whose UTF-8 contents reach `out` as6983
/// Latin-1, which is what ICCCM says STRING holds. Text past `RELAY_CAP` is70
-/// dropped along with the transfer.84
+/// dropped along with the transfer, and so is a transfer that moves nothing85
+/// for `RELAY_IDLE`.7186
fn latin1_relay(handle: &LoopHandle<'static, State>, out: OwnedFd) -> std::io::Result<OwnedFd> {7287
let (read, write) = std::io::pipe()?;7388
fcntl_setfl(&read, OFlags::NONBLOCK)?;7489
fcntl_setfl(&out, OFlags::NONBLOCK)?;90
+ let relay = Rc::new(Relay {91
+ source: Cell::new(None),92
+ moved: Cell::new(Instant::now()),93
+ });7594
let mut out = Some(File::from(out));7695
let mut text = Vec::new();77
- let next = handle.clone();78
- handle96
+ let (next, shared) = (handle.clone(), relay.clone());97
+ let token = handle7998
.insert_source(8099
Generic::new(read, Interest::READ, Mode::Level),81100
move |_, read, _| {@@ -83,33 +102,54 @@ fn latin1_relay(handle: &LoopHandle<'static, State>, out: OwnedFd) -> std::io::R
83102
loop {84103
match (&**read).read(&mut chunk) {85104
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]),105
+ Ok(n) if text.len() + n > RELAY_CAP => {106
+ shared.source.set(None);107
+ return Ok(PostAction::Remove);108
+ }109
+ Ok(n) => {110
+ text.extend_from_slice(&chunk[..n]);111
+ shared.moved.set(Instant::now());112
+ }88113
Err(e) if e.kind() == ErrorKind::Interrupted => {}89114
Err(e) if e.kind() == ErrorKind::WouldBlock => {90115
return Ok(PostAction::Continue);91116
}92
- Err(_) => return Ok(PostAction::Remove),117
+ Err(_) => {118
+ shared.source.set(None);119
+ return Ok(PostAction::Remove);120
+ }93121
}94122
}123
+ shared.source.set(None);95124
if let Some(out) = out.take() {96
- write_when_ready(&next, out, latin1(&text));125
+ write_when_ready(&next, &shared, out, latin1(&text));97126
}98127
Ok(PostAction::Remove)99128
},100129
)101130
.map_err(|e| std::io::Error::other(e.to_string()))?;131
+ relay.source.set(Some(token));132
+ expire_when_stalled(handle, relay);102133
Ok(write.into())103134
}104135
105
-fn write_when_ready(handle: &LoopHandle<'static, State>, out: File, data: Vec<u8>) {136
+fn write_when_ready(137
+ handle: &LoopHandle<'static, State>,138
+ relay: &Rc<Relay>,139
+ out: File,140
+ data: Vec<u8>,141
+) {106142
let mut sent = 0;143
+ let shared = relay.clone();107144
let inserted = handle.insert_source(108145
Generic::new(out, Interest::WRITE, Mode::Level),109146
move |_, out, _| {110147
while sent < data.len() {111148
match (&**out).write(&data[sent..]) {112
- Ok(n) => sent += n,149
+ Ok(n) => {150
+ sent += n;151
+ shared.moved.set(Instant::now());152
+ }113153
Err(e) if e.kind() == ErrorKind::Interrupted => {}114154
Err(e) if e.kind() == ErrorKind::WouldBlock => {115155
return Ok(PostAction::Continue);@@ -117,14 +157,32 @@ fn write_when_ready(handle: &LoopHandle<'static, State>, out: File, data: Vec<u8
117157
Err(_) => break,118158
}119159
}160
+ shared.source.set(None);120161
Ok(PostAction::Remove)121162
},122163
);123
- if let Err(e) = inserted {124
- tracing::warn!("cannot relay STRING to X11: {e}");164
+ match inserted {165
+ Ok(token) => relay.source.set(Some(token)),166
+ Err(e) => tracing::warn!("cannot relay STRING to X11: {e}"),125167
}126168
}127169
170
+fn expire_when_stalled(handle: &LoopHandle<'static, State>, relay: Rc<Relay>) {171
+ let owner = handle.clone();172
+ let _ = handle.insert_source(Timer::from_duration(RELAY_IDLE), move |_, _, _| {173
+ let Some(token) = relay.source.get() else {174
+ return TimeoutAction::Drop;175
+ };176
+ let idle = relay.moved.get().elapsed();177
+ if idle < RELAY_IDLE {178
+ return TimeoutAction::ToDuration(RELAY_IDLE - idle);179
+ }180
+ relay.source.set(None);181
+ owner.remove(token);182
+ TimeoutAction::Drop183
+ });184
+}185
+128186
pub struct Xwayland {129187
pub display: u32,130188
listeners: Vec<UnixListener>,