atagen

monolith

public
Star0
Watch0

Commit 1df9b00ad0fd

tarball

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

3 files changed+40-4
xwayland: make the X11 selection read policy configurable

Allowing only the focused X11 window to read a Wayland selection locks out X clipboard managers and headless xclip, which never hold focus. The new [xwayland] selection_access option keeps focused as the default and adds any for users who want those tools.
Modified file: crates/config/default.toml+6 additions
@@ -73,6 +73,12 @@ layout = ""
7373 repeat_delay = 200
7474 repeat_rate = 25
7575
76+[xwayland]
77+# Which X11 clients may read a Wayland-owned clipboard or primary selection.
78+# focused allows only the window holding keyboard focus, any allows every X11
79+# client, which clipboard managers and headless xclip need.
80+selection_access = "focused"
81+
7682 # libinput settings, bare metal only. Anything left out keeps the device's
7783 # own default, and options a device lacks are skipped. Touchpads are devices
7884 # that can tap, every other pointing device takes [input.pointer].
Modified file: crates/config/src/lib.rs+28 additions
@@ -19,6 +19,7 @@ pub struct Config {
1919 pub input: Input,
2020 pub idle: Idle,
2121 pub lid: Lid,
22+ pub xwayland: Xwayland,
2223 pub binds: Vec<Bind>,
2324 pub rules: Vec<Rule>,
2425 pub startup: Vec<Vec<String>>,
@@ -68,6 +69,30 @@ impl Default for General {
6869 }
6970 }
7071
72+#[derive(Deserialize, Debug, Clone, PartialEq)]
73+#[serde(default, deny_unknown_fields)]
74+pub struct Xwayland {
75+ /// Which X11 clients may read a Wayland-owned selection.
76+ pub selection_access: SelectionAccess,
77+}
78+
79+impl Default for Xwayland {
80+ fn default() -> Self {
81+ Self {
82+ selection_access: SelectionAccess::Focused,
83+ }
84+ }
85+}
86+
87+#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
88+#[serde(rename_all = "lowercase")]
89+pub enum SelectionAccess {
90+ /// Only the X11 window holding keyboard focus.
91+ Focused,
92+ /// Every X11 client, which clipboard managers and headless tools need.
93+ Any,
94+}
95+
7196 #[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
7297 #[serde(default, deny_unknown_fields)]
7398 pub struct Gaps {
@@ -548,6 +573,7 @@ impl Config {
548573 let idle = section("idle", table.remove("idle"), w);
549574 let lid = section("lid", table.remove("lid"), w);
550575 let animation = section("animation", table.remove("animation"), w);
576+ let xwayland = section("xwayland", table.remove("xwayland"), w);
551577 let decoration = section("decoration", table.remove("decoration"), w);
552578 let output: Outputs = section("output", table.remove("output"), w);
553579 for name in output.named.keys().filter(|n| !connector_like(n)) {
@@ -608,6 +634,7 @@ impl Config {
608634 input,
609635 idle,
610636 lid,
637+ xwayland,
611638 binds,
612639 rules,
613640 startup,
@@ -798,6 +825,7 @@ mod tests {
798825 assert_eq!(c.input, Input::default());
799826 assert_eq!(c.idle, Idle::default());
800827 assert_eq!(c.lid, Lid::default());
828+ assert_eq!(c.xwayland, Xwayland::default());
801829 assert!(c.binds.len() > 40);
802830 }
803831
Modified file: crates/monolith/src/xwayland.rs+6 additions−4 deletions
@@ -5,6 +5,7 @@
55
66 use crate::focus::Focus;
77 use crate::state::State;
8+use monolith_config::SelectionAccess;
89 use smithay::desktop::Window;
910 use smithay::reexports::calloop::generic::Generic;
1011 use smithay::reexports::calloop::{Interest, Mode, PostAction, RegistrationToken};
@@ -357,10 +358,11 @@ impl XwmHandler for State {
357358 }
358359
359360 fn allow_selection_access(&mut self, xwm: XwmId, _selection: SelectionTarget) -> bool {
360- matches!(
361- self.input.seat.get_keyboard().and_then(|k| k.current_focus()),
362- Some(Focus::X11(x)) if x.xwm_id() == Some(xwm)
363- )
361+ self.settings.config.xwayland.selection_access == SelectionAccess::Any
362+ || matches!(
363+ self.input.seat.get_keyboard().and_then(|k| k.current_focus()),
364+ Some(Focus::X11(x)) if x.xwm_id() == Some(xwm)
365+ )
364366 }
365367
366368 fn send_selection(