atagen

monolith

public
Star0
Watch0
47b86d08d05a97fea42c27b6ed194c0ceaa30374
1
ssh://brewery@pls.lobotomise.me:33/atagen/monolith.git

Blame: crates/monolith/src/xwayland.rs

cce3ef7e0162atagen2026-10-16 22:31 UTC1
//! Lazy Xwayland. monolith owns an X display socket from startup and only
cce3ef7e0162atagen2026-10-16 22:31 UTC2
//! starts Xwayland when a client connects, handing it the socket so the
cce3ef7e0162atagen2026-10-16 22:31 UTC3
//! connection that woke it is served. Xwayland exits once its last client
cce3ef7e0162atagen2026-10-16 22:31 UTC4
//! leaves and the socket is watched again.
cce3ef7e0162atagen2026-10-16 22:31 UTC5
cce3ef7e0162atagen2026-10-16 22:31 UTC6
use crate::state::State;
cce3ef7e0162atagen2026-10-16 22:31 UTC7
use smithay::desktop::Window;
cce3ef7e0162atagen2026-10-16 22:31 UTC8
use smithay::reexports::calloop::generic::Generic;
cce3ef7e0162atagen2026-10-16 22:31 UTC9
use smithay::reexports::calloop::{Interest, Mode, PostAction, RegistrationToken};
cce3ef7e0162atagen2026-10-16 22:31 UTC10
use smithay::reexports::rustix::io::{FdFlags, fcntl_getfd, fcntl_setfd};
cce3ef7e0162atagen2026-10-16 22:31 UTC11
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
754ca2132943atagen2026-10-19 07:29 UTC12
use smithay::utils::{Logical, Rectangle, Size};
cce3ef7e0162atagen2026-10-16 22:31 UTC13
use smithay::wayland::xwayland_shell::{XWaylandShellHandler, XWaylandShellState};
272bdf93aeadatagen2026-10-17 03:12 UTC14
use smithay::xwayland::xwm::{Reorder, ResizeEdge, WmWindowProperty, WmWindowType, XwmId};
cce3ef7e0162atagen2026-10-16 22:31 UTC15
use smithay::xwayland::{X11Surface, X11Wm, XWayland, XWaylandEvent, XwmHandler};
cce3ef7e0162atagen2026-10-16 22:31 UTC16
use std::os::fd::AsFd;
cce3ef7e0162atagen2026-10-16 22:31 UTC17
use std::os::linux::net::SocketAddrExt;
cce3ef7e0162atagen2026-10-16 22:31 UTC18
use std::os::unix::net::{SocketAddr, UnixListener};
cce3ef7e0162atagen2026-10-16 22:31 UTC19
use std::process::Stdio;
cce3ef7e0162atagen2026-10-16 22:31 UTC20
cce3ef7e0162atagen2026-10-16 22:31 UTC21
pub struct Xwayland {
cce3ef7e0162atagen2026-10-16 22:31 UTC22
    pub display: u32,
cce3ef7e0162atagen2026-10-16 22:31 UTC23
    listeners: Vec<UnixListener>,
cce3ef7e0162atagen2026-10-16 22:31 UTC24
    watch: Vec<(usize, RegistrationToken)>,
cce3ef7e0162atagen2026-10-16 22:31 UTC25
    running: bool,
cce3ef7e0162atagen2026-10-16 22:31 UTC26
    pub wm: Option<X11Wm>,
cce3ef7e0162atagen2026-10-16 22:31 UTC27
    /// Override-redirect windows such as menus and tooltips, bottom to top.
cce3ef7e0162atagen2026-10-16 22:31 UTC28
    pub unmanaged: Vec<X11Surface>,
cce3ef7e0162atagen2026-10-16 22:31 UTC29
}
cce3ef7e0162atagen2026-10-16 22:31 UTC30
cce3ef7e0162atagen2026-10-16 22:31 UTC31
impl Drop for Xwayland {
cce3ef7e0162atagen2026-10-16 22:31 UTC32
    fn drop(&mut self) {
cce3ef7e0162atagen2026-10-16 22:31 UTC33
        let _ = std::fs::remove_file(socket_path(self.display));
cce3ef7e0162atagen2026-10-16 22:31 UTC34
        let _ = std::fs::remove_file(lock_path(self.display));
cce3ef7e0162atagen2026-10-16 22:31 UTC35
    }
cce3ef7e0162atagen2026-10-16 22:31 UTC36
}
cce3ef7e0162atagen2026-10-16 22:31 UTC37
cce3ef7e0162atagen2026-10-16 22:31 UTC38
fn socket_path(n: u32) -> String {
cce3ef7e0162atagen2026-10-16 22:31 UTC39
    format!("/tmp/.X11-unix/X{n}")
cce3ef7e0162atagen2026-10-16 22:31 UTC40
}
cce3ef7e0162atagen2026-10-16 22:31 UTC41
cce3ef7e0162atagen2026-10-16 22:31 UTC42
fn lock_path(n: u32) -> String {
cce3ef7e0162atagen2026-10-16 22:31 UTC43
    format!("/tmp/.X{n}-lock")
cce3ef7e0162atagen2026-10-16 22:31 UTC44
}
cce3ef7e0162atagen2026-10-16 22:31 UTC45
cce3ef7e0162atagen2026-10-16 22:31 UTC46
/// Claims the first free X display, taking over stale locks left by dead
cce3ef7e0162atagen2026-10-16 22:31 UTC47
/// servers.
cce3ef7e0162atagen2026-10-16 22:31 UTC48
fn claim() -> Option<(u32, Vec<UnixListener>)> {
cce3ef7e0162atagen2026-10-16 22:31 UTC49
    let _ = std::fs::create_dir_all("/tmp/.X11-unix");
cce3ef7e0162atagen2026-10-16 22:31 UTC50
    for n in 0..64 {
cce3ef7e0162atagen2026-10-16 22:31 UTC51
        let lock = lock_path(n);
cce3ef7e0162atagen2026-10-16 22:31 UTC52
        let created = std::fs::OpenOptions::new()
cce3ef7e0162atagen2026-10-16 22:31 UTC53
            .write(true)
cce3ef7e0162atagen2026-10-16 22:31 UTC54
            .create_new(true)
cce3ef7e0162atagen2026-10-16 22:31 UTC55
            .open(&lock);
cce3ef7e0162atagen2026-10-16 22:31 UTC56
        let file = match created {
cce3ef7e0162atagen2026-10-16 22:31 UTC57
            Ok(f) => f,
cce3ef7e0162atagen2026-10-16 22:31 UTC58
            Err(_) => {
cce3ef7e0162atagen2026-10-16 22:31 UTC59
                let owner = std::fs::read_to_string(&lock)
cce3ef7e0162atagen2026-10-16 22:31 UTC60
                    .ok()
cce3ef7e0162atagen2026-10-16 22:31 UTC61
                    .and_then(|s| s.trim().parse::<u32>().ok());
28e12a7e36cdatagen2026-10-17 05:11 UTC62
                let stale = owner.is_some_and(|pid| !holds_display(pid));
cce3ef7e0162atagen2026-10-16 22:31 UTC63
                if !stale || std::fs::remove_file(&lock).is_err() {
cce3ef7e0162atagen2026-10-16 22:31 UTC64
                    continue;
cce3ef7e0162atagen2026-10-16 22:31 UTC65
                }
cce3ef7e0162atagen2026-10-16 22:31 UTC66
                match std::fs::OpenOptions::new()
cce3ef7e0162atagen2026-10-16 22:31 UTC67
                    .write(true)
cce3ef7e0162atagen2026-10-16 22:31 UTC68
                    .create_new(true)
cce3ef7e0162atagen2026-10-16 22:31 UTC69
                    .open(&lock)
cce3ef7e0162atagen2026-10-16 22:31 UTC70
                {
cce3ef7e0162atagen2026-10-16 22:31 UTC71
                    Ok(f) => f,
cce3ef7e0162atagen2026-10-16 22:31 UTC72
                    Err(_) => continue,
cce3ef7e0162atagen2026-10-16 22:31 UTC73
                }
cce3ef7e0162atagen2026-10-16 22:31 UTC74
            }
cce3ef7e0162atagen2026-10-16 22:31 UTC75
        };
cce3ef7e0162atagen2026-10-16 22:31 UTC76
        use std::io::Write;
cce3ef7e0162atagen2026-10-16 22:31 UTC77
        let _ = writeln!(&file, "{:>10}", std::process::id());
cce3ef7e0162atagen2026-10-16 22:31 UTC78
        let path = socket_path(n);
cce3ef7e0162atagen2026-10-16 22:31 UTC79
        let _ = std::fs::remove_file(&path);
cce3ef7e0162atagen2026-10-16 22:31 UTC80
        let Ok(fs) = UnixListener::bind(&path) else {
cce3ef7e0162atagen2026-10-16 22:31 UTC81
            let _ = std::fs::remove_file(&lock);
cce3ef7e0162atagen2026-10-16 22:31 UTC82
            continue;
cce3ef7e0162atagen2026-10-16 22:31 UTC83
        };
cce3ef7e0162atagen2026-10-16 22:31 UTC84
        let mut listeners = vec![fs];
cce3ef7e0162atagen2026-10-16 22:31 UTC85
        if let Ok(addr) = SocketAddr::from_abstract_name(path.as_bytes())
cce3ef7e0162atagen2026-10-16 22:31 UTC86
            && let Ok(abs) = UnixListener::bind_addr(&addr)
cce3ef7e0162atagen2026-10-16 22:31 UTC87
        {
cce3ef7e0162atagen2026-10-16 22:31 UTC88
            listeners.push(abs);
cce3ef7e0162atagen2026-10-16 22:31 UTC89
        }
cce3ef7e0162atagen2026-10-16 22:31 UTC90
        return Some((n, listeners));
cce3ef7e0162atagen2026-10-16 22:31 UTC91
    }
cce3ef7e0162atagen2026-10-16 22:31 UTC92
    None
cce3ef7e0162atagen2026-10-16 22:31 UTC93
}
cce3ef7e0162atagen2026-10-16 22:31 UTC94
cce3ef7e0162atagen2026-10-16 22:31 UTC95
pub fn init(state: &mut State) {
cce3ef7e0162atagen2026-10-16 22:31 UTC96
    let Some((number, listeners)) = claim() else {
cce3ef7e0162atagen2026-10-16 22:31 UTC97
        tracing::warn!("no free X display, X11 clients will not run");
cce3ef7e0162atagen2026-10-16 22:31 UTC98
        return;
cce3ef7e0162atagen2026-10-16 22:31 UTC99
    };
cce3ef7e0162atagen2026-10-16 22:31 UTC100
    let mut watch = Vec::new();
cce3ef7e0162atagen2026-10-16 22:31 UTC101
    for (i, l) in listeners.iter().enumerate() {
cce3ef7e0162atagen2026-10-16 22:31 UTC102
        let Ok(l) = l.try_clone() else { continue };
a772647a949fatagen2026-10-17 02:10 UTC103
        let token = state.core.handle.insert_source(
cce3ef7e0162atagen2026-10-16 22:31 UTC104
            Generic::new(l, Interest::READ, Mode::Level),
cce3ef7e0162atagen2026-10-16 22:31 UTC105
            move |_, _, state| {
cce3ef7e0162atagen2026-10-16 22:31 UTC106
                start(state, i);
cce3ef7e0162atagen2026-10-16 22:31 UTC107
                Ok(PostAction::Disable)
cce3ef7e0162atagen2026-10-16 22:31 UTC108
            },
cce3ef7e0162atagen2026-10-16 22:31 UTC109
        );
cce3ef7e0162atagen2026-10-16 22:31 UTC110
        if let Ok(t) = token {
cce3ef7e0162atagen2026-10-16 22:31 UTC111
            watch.push((i, t));
cce3ef7e0162atagen2026-10-16 22:31 UTC112
        }
cce3ef7e0162atagen2026-10-16 22:31 UTC113
    }
cce3ef7e0162atagen2026-10-16 22:31 UTC114
    tracing::info!("X11 clients will start Xwayland on :{number}");
c7e08bac3e01atagen2026-10-17 02:45 UTC115
    state.services.xwayland = Some(Xwayland {
cce3ef7e0162atagen2026-10-16 22:31 UTC116
        display: number,
cce3ef7e0162atagen2026-10-16 22:31 UTC117
        listeners,
cce3ef7e0162atagen2026-10-16 22:31 UTC118
        watch,
cce3ef7e0162atagen2026-10-16 22:31 UTC119
        running: false,
cce3ef7e0162atagen2026-10-16 22:31 UTC120
        wm: None,
cce3ef7e0162atagen2026-10-16 22:31 UTC121
        unmanaged: Vec::new(),
cce3ef7e0162atagen2026-10-16 22:31 UTC122
    });
cce3ef7e0162atagen2026-10-16 22:31 UTC123
}
cce3ef7e0162atagen2026-10-16 22:31 UTC124
cce3ef7e0162atagen2026-10-16 22:31 UTC125
/// Waits for the next X client to knock.
cce3ef7e0162atagen2026-10-16 22:31 UTC126
fn rearm(state: &mut State) {
c7e08bac3e01atagen2026-10-17 02:45 UTC127
    let Some(x) = state.services.xwayland.as_mut() else {
cce3ef7e0162atagen2026-10-16 22:31 UTC128
        return;
cce3ef7e0162atagen2026-10-16 22:31 UTC129
    };
cce3ef7e0162atagen2026-10-16 22:31 UTC130
    x.running = false;
cce3ef7e0162atagen2026-10-16 22:31 UTC131
    for (_, t) in &x.watch {
a772647a949fatagen2026-10-17 02:10 UTC132
        let _ = state.core.handle.enable(t);
cce3ef7e0162atagen2026-10-16 22:31 UTC133
    }
cce3ef7e0162atagen2026-10-16 22:31 UTC134
}
cce3ef7e0162atagen2026-10-16 22:31 UTC135
cce3ef7e0162atagen2026-10-16 22:31 UTC136
fn set_inherit(listeners: &[UnixListener], inherit: bool) {
cce3ef7e0162atagen2026-10-16 22:31 UTC137
    for l in listeners {
cce3ef7e0162atagen2026-10-16 22:31 UTC138
        let flags = fcntl_getfd(l.as_fd()).unwrap_or(FdFlags::CLOEXEC);
cce3ef7e0162atagen2026-10-16 22:31 UTC139
        let flags = if inherit {
cce3ef7e0162atagen2026-10-16 22:31 UTC140
            flags - FdFlags::CLOEXEC
cce3ef7e0162atagen2026-10-16 22:31 UTC141
        } else {
cce3ef7e0162atagen2026-10-16 22:31 UTC142
            flags | FdFlags::CLOEXEC
cce3ef7e0162atagen2026-10-16 22:31 UTC143
        };
cce3ef7e0162atagen2026-10-16 22:31 UTC144
        let _ = fcntl_setfd(l.as_fd(), flags);
cce3ef7e0162atagen2026-10-16 22:31 UTC145
    }
cce3ef7e0162atagen2026-10-16 22:31 UTC146
}
cce3ef7e0162atagen2026-10-16 22:31 UTC147
cce3ef7e0162atagen2026-10-16 22:31 UTC148
fn start(state: &mut State, fired: usize) {
c7e08bac3e01atagen2026-10-17 02:45 UTC149
    let Some(x) = state.services.xwayland.as_mut() else {
cce3ef7e0162atagen2026-10-16 22:31 UTC150
        return;
cce3ef7e0162atagen2026-10-16 22:31 UTC151
    };
cce3ef7e0162atagen2026-10-16 22:31 UTC152
    if x.running {
cce3ef7e0162atagen2026-10-16 22:31 UTC153
        return;
cce3ef7e0162atagen2026-10-16 22:31 UTC154
    }
cce3ef7e0162atagen2026-10-16 22:31 UTC155
    x.running = true;
cce3ef7e0162atagen2026-10-16 22:31 UTC156
    for (i, t) in &x.watch {
cce3ef7e0162atagen2026-10-16 22:31 UTC157
        if *i != fired {
a772647a949fatagen2026-10-17 02:10 UTC158
            state.core.handle.disable(t).ok();
cce3ef7e0162atagen2026-10-16 22:31 UTC159
        }
cce3ef7e0162atagen2026-10-16 22:31 UTC160
    }
cce3ef7e0162atagen2026-10-16 22:31 UTC161
    let mut args = Vec::new();
cce3ef7e0162atagen2026-10-16 22:31 UTC162
    for l in &x.listeners {
cce3ef7e0162atagen2026-10-16 22:31 UTC163
        args.push("-listenfd".to_string());
cce3ef7e0162atagen2026-10-16 22:31 UTC164
        args.push(std::os::fd::AsRawFd::as_raw_fd(l).to_string());
cce3ef7e0162atagen2026-10-16 22:31 UTC165
    }
cce3ef7e0162atagen2026-10-16 22:31 UTC166
    // smithay only passes on the sockets it creates itself, so ours are
cce3ef7e0162atagen2026-10-16 22:31 UTC167
    // made inheritable just for this one spawn.
cce3ef7e0162atagen2026-10-16 22:31 UTC168
    set_inherit(&x.listeners, true);
cce3ef7e0162atagen2026-10-16 22:31 UTC169
    let spawned = XWayland::spawn(
a772647a949fatagen2026-10-17 02:10 UTC170
        &state.core.display,
cce3ef7e0162atagen2026-10-16 22:31 UTC171
        None,
cce3ef7e0162atagen2026-10-16 22:31 UTC172
        std::iter::empty::<(String, String)>(),
cce3ef7e0162atagen2026-10-16 22:31 UTC173
        args,
cce3ef7e0162atagen2026-10-16 22:31 UTC174
        false,
cce3ef7e0162atagen2026-10-16 22:31 UTC175
        Stdio::null(),
cce3ef7e0162atagen2026-10-16 22:31 UTC176
        Stdio::null(),
cce3ef7e0162atagen2026-10-16 22:31 UTC177
        |_| {},
cce3ef7e0162atagen2026-10-16 22:31 UTC178
    );
cce3ef7e0162atagen2026-10-16 22:31 UTC179
    set_inherit(&x.listeners, false);
cce3ef7e0162atagen2026-10-16 22:31 UTC180
    let (xwayland, client) = match spawned {
cce3ef7e0162atagen2026-10-16 22:31 UTC181
        Ok(s) => s,
cce3ef7e0162atagen2026-10-16 22:31 UTC182
        Err(e) => {
cce3ef7e0162atagen2026-10-16 22:31 UTC183
            tracing::error!("cannot start Xwayland, X11 clients will not run: {e}");
cce3ef7e0162atagen2026-10-16 22:31 UTC184
            return;
cce3ef7e0162atagen2026-10-16 22:31 UTC185
        }
cce3ef7e0162atagen2026-10-16 22:31 UTC186
    };
cce3ef7e0162atagen2026-10-16 22:31 UTC187
    tracing::info!("starting Xwayland");
cce3ef7e0162atagen2026-10-16 22:31 UTC188
    let inserted = state
a772647a949fatagen2026-10-17 02:10 UTC189
        .core
cce3ef7e0162atagen2026-10-16 22:31 UTC190
        .handle
cce3ef7e0162atagen2026-10-16 22:31 UTC191
        .insert_source(xwayland, move |event, _, state| match event {
cce3ef7e0162atagen2026-10-16 22:31 UTC192
            XWaylandEvent::Ready { x11_socket, .. } => {
a772647a949fatagen2026-10-17 02:10 UTC193
                let dh = state.core.display.clone();
a772647a949fatagen2026-10-17 02:10 UTC194
                match X11Wm::start_wm(state.core.handle.clone(), &dh, x11_socket, client.clone()) {
cce3ef7e0162atagen2026-10-16 22:31 UTC195
                    Ok(wm) => {
cce3ef7e0162atagen2026-10-16 22:31 UTC196
                        tracing::info!("Xwayland ready");
c7e08bac3e01atagen2026-10-17 02:45 UTC197
                        if let Some(x) = state.services.xwayland.as_mut() {
cce3ef7e0162atagen2026-10-16 22:31 UTC198
                            x.wm = Some(wm);
cce3ef7e0162atagen2026-10-16 22:31 UTC199
                        }
cce3ef7e0162atagen2026-10-16 22:31 UTC200
                    }
cce3ef7e0162atagen2026-10-16 22:31 UTC201
                    Err(e) => tracing::error!("cannot manage Xwayland windows: {e}"),
cce3ef7e0162atagen2026-10-16 22:31 UTC202
                }
cce3ef7e0162atagen2026-10-16 22:31 UTC203
            }
cce3ef7e0162atagen2026-10-16 22:31 UTC204
            XWaylandEvent::Error => {
cce3ef7e0162atagen2026-10-16 22:31 UTC205
                tracing::warn!("Xwayland failed to start");
cce3ef7e0162atagen2026-10-16 22:31 UTC206
                rearm(state);
cce3ef7e0162atagen2026-10-16 22:31 UTC207
            }
cce3ef7e0162atagen2026-10-16 22:31 UTC208
        });
cce3ef7e0162atagen2026-10-16 22:31 UTC209
    if let Err(e) = inserted {
cce3ef7e0162atagen2026-10-16 22:31 UTC210
        tracing::error!("cannot watch Xwayland: {e}");
cce3ef7e0162atagen2026-10-16 22:31 UTC211
    }
cce3ef7e0162atagen2026-10-16 22:31 UTC212
}
cce3ef7e0162atagen2026-10-16 22:31 UTC213
cce3ef7e0162atagen2026-10-16 22:31 UTC214
impl XWaylandShellHandler for State {
cce3ef7e0162atagen2026-10-16 22:31 UTC215
    fn xwayland_shell_state(&mut self) -> &mut XWaylandShellState {
a772647a949fatagen2026-10-17 02:10 UTC216
        &mut self.protocols.xwayland_shell_state
cce3ef7e0162atagen2026-10-16 22:31 UTC217
    }
cce3ef7e0162atagen2026-10-16 22:31 UTC218
60956e3999deatagen2026-10-17 02:50 UTC219
    fn surface_associated(&mut self, _xwm: XwmId, surface: WlSurface, window: X11Surface) {
60956e3999deatagen2026-10-17 02:50 UTC220
        if let Some(id) = self.x11_window_id(&window) {
60956e3999deatagen2026-10-17 02:50 UTC221
            self.wm.index(id);
60956e3999deatagen2026-10-17 02:50 UTC222
        }
cce3ef7e0162atagen2026-10-16 22:31 UTC223
        self.on_buffer(&surface);
7e58494265dbatagen2026-10-17 02:56 UTC224
        self.dirty.redraw = true;
cce3ef7e0162atagen2026-10-16 22:31 UTC225
    }
cce3ef7e0162atagen2026-10-16 22:31 UTC226
}
cce3ef7e0162atagen2026-10-16 22:31 UTC227
cce3ef7e0162atagen2026-10-16 22:31 UTC228
impl XwmHandler for State {
cce3ef7e0162atagen2026-10-16 22:31 UTC229
    fn xwm_state(&mut self, _xwm: XwmId) -> &mut X11Wm {
c7e08bac3e01atagen2026-10-17 02:45 UTC230
        self.services
c7e08bac3e01atagen2026-10-17 02:45 UTC231
            .xwayland
cce3ef7e0162atagen2026-10-16 22:31 UTC232
            .as_mut()
cce3ef7e0162atagen2026-10-16 22:31 UTC233
            .and_then(|x| x.wm.as_mut())
cce3ef7e0162atagen2026-10-16 22:31 UTC234
            .expect("X11 events only arrive while the WM runs")
cce3ef7e0162atagen2026-10-16 22:31 UTC235
    }
cce3ef7e0162atagen2026-10-16 22:31 UTC236
cce3ef7e0162atagen2026-10-16 22:31 UTC237
    fn new_window(&mut self, _xwm: XwmId, _window: X11Surface) {}
cce3ef7e0162atagen2026-10-16 22:31 UTC238
cce3ef7e0162atagen2026-10-16 22:31 UTC239
    fn new_override_redirect_window(&mut self, _xwm: XwmId, _window: X11Surface) {}
cce3ef7e0162atagen2026-10-16 22:31 UTC240
cce3ef7e0162atagen2026-10-16 22:31 UTC241
    fn map_window_request(&mut self, _xwm: XwmId, window: X11Surface) {
cce3ef7e0162atagen2026-10-16 22:31 UTC242
        if let Err(e) = window.set_mapped(true) {
cce3ef7e0162atagen2026-10-16 22:31 UTC243
            tracing::warn!("cannot map X11 window: {e}");
cce3ef7e0162atagen2026-10-16 22:31 UTC244
            return;
cce3ef7e0162atagen2026-10-16 22:31 UTC245
        }
cce3ef7e0162atagen2026-10-16 22:31 UTC246
        let float = floats(&window).then(|| window.geometry());
cce3ef7e0162atagen2026-10-16 22:31 UTC247
        self.map_window(Window::new_x11_window(window), float);
cce3ef7e0162atagen2026-10-16 22:31 UTC248
    }
cce3ef7e0162atagen2026-10-16 22:31 UTC249
cce3ef7e0162atagen2026-10-16 22:31 UTC250
    fn mapped_override_redirect_window(&mut self, _xwm: XwmId, window: X11Surface) {
c7e08bac3e01atagen2026-10-17 02:45 UTC251
        if let Some(x) = self.services.xwayland.as_mut() {
cce3ef7e0162atagen2026-10-16 22:31 UTC252
            x.unmanaged.retain(|w| w != &window);
cce3ef7e0162atagen2026-10-16 22:31 UTC253
            x.unmanaged.push(window);
cce3ef7e0162atagen2026-10-16 22:31 UTC254
        }
7e58494265dbatagen2026-10-17 02:56 UTC255
        self.dirty.redraw = true;
cce3ef7e0162atagen2026-10-16 22:31 UTC256
    }
cce3ef7e0162atagen2026-10-16 22:31 UTC257
cce3ef7e0162atagen2026-10-16 22:31 UTC258
    fn unmapped_window(&mut self, _xwm: XwmId, window: X11Surface) {
cce3ef7e0162atagen2026-10-16 22:31 UTC259
        self.forget_x11(&window);
cce3ef7e0162atagen2026-10-16 22:31 UTC260
        if !window.is_override_redirect() {
0e6ab4933125atagen2026-10-17 05:08 UTC261
            logged("unmap", window.set_mapped(false));
cce3ef7e0162atagen2026-10-16 22:31 UTC262
        }
cce3ef7e0162atagen2026-10-16 22:31 UTC263
    }
cce3ef7e0162atagen2026-10-16 22:31 UTC264
cce3ef7e0162atagen2026-10-16 22:31 UTC265
    fn destroyed_window(&mut self, _xwm: XwmId, window: X11Surface) {
cce3ef7e0162atagen2026-10-16 22:31 UTC266
        self.forget_x11(&window);
cce3ef7e0162atagen2026-10-16 22:31 UTC267
    }
cce3ef7e0162atagen2026-10-16 22:31 UTC268
cce3ef7e0162atagen2026-10-16 22:31 UTC269
    fn configure_request(
cce3ef7e0162atagen2026-10-16 22:31 UTC270
        &mut self,
cce3ef7e0162atagen2026-10-16 22:31 UTC271
        _xwm: XwmId,
cce3ef7e0162atagen2026-10-16 22:31 UTC272
        window: X11Surface,
cce3ef7e0162atagen2026-10-16 22:31 UTC273
        x: Option<i32>,
cce3ef7e0162atagen2026-10-16 22:31 UTC274
        y: Option<i32>,
cce3ef7e0162atagen2026-10-16 22:31 UTC275
        w: Option<u32>,
cce3ef7e0162atagen2026-10-16 22:31 UTC276
        h: Option<u32>,
cce3ef7e0162atagen2026-10-16 22:31 UTC277
        _reorder: Option<Reorder>,
cce3ef7e0162atagen2026-10-16 22:31 UTC278
    ) {
cce3ef7e0162atagen2026-10-16 22:31 UTC279
        if self.x11_window_id(&window).is_some() {
0e6ab4933125atagen2026-10-17 05:08 UTC280
            logged("configure", window.configure(None));
cce3ef7e0162atagen2026-10-16 22:31 UTC281
            return;
cce3ef7e0162atagen2026-10-16 22:31 UTC282
        }
cce3ef7e0162atagen2026-10-16 22:31 UTC283
        let mut geo = window.geometry();
cce3ef7e0162atagen2026-10-16 22:31 UTC284
        geo.loc.x = x.unwrap_or(geo.loc.x);
cce3ef7e0162atagen2026-10-16 22:31 UTC285
        geo.loc.y = y.unwrap_or(geo.loc.y);
cce3ef7e0162atagen2026-10-16 22:31 UTC286
        geo.size.w = w.map_or(geo.size.w, |w| w as i32);
cce3ef7e0162atagen2026-10-16 22:31 UTC287
        geo.size.h = h.map_or(geo.size.h, |h| h as i32);
0e6ab4933125atagen2026-10-17 05:08 UTC288
        logged("configure", window.configure(geo));
cce3ef7e0162atagen2026-10-16 22:31 UTC289
    }
cce3ef7e0162atagen2026-10-16 22:31 UTC290
cce3ef7e0162atagen2026-10-16 22:31 UTC291
    fn configure_notify(
cce3ef7e0162atagen2026-10-16 22:31 UTC292
        &mut self,
cce3ef7e0162atagen2026-10-16 22:31 UTC293
        _xwm: XwmId,
cce3ef7e0162atagen2026-10-16 22:31 UTC294
        window: X11Surface,
cce3ef7e0162atagen2026-10-16 22:31 UTC295
        _geometry: Rectangle<i32, Logical>,
cce3ef7e0162atagen2026-10-16 22:31 UTC296
        _above: Option<smithay::xwayland::xwm::X11Window>,
cce3ef7e0162atagen2026-10-16 22:31 UTC297
    ) {
cce3ef7e0162atagen2026-10-16 22:31 UTC298
        if window.is_override_redirect() {
7e58494265dbatagen2026-10-17 02:56 UTC299
            self.dirty.redraw = true;
cce3ef7e0162atagen2026-10-16 22:31 UTC300
        }
cce3ef7e0162atagen2026-10-16 22:31 UTC301
    }
cce3ef7e0162atagen2026-10-16 22:31 UTC302
cce3ef7e0162atagen2026-10-16 22:31 UTC303
    fn resize_request(
cce3ef7e0162atagen2026-10-16 22:31 UTC304
        &mut self,
cce3ef7e0162atagen2026-10-16 22:31 UTC305
        _xwm: XwmId,
cce3ef7e0162atagen2026-10-16 22:31 UTC306
        _window: X11Surface,
cce3ef7e0162atagen2026-10-16 22:31 UTC307
        _button: u32,
cce3ef7e0162atagen2026-10-16 22:31 UTC308
        _edge: ResizeEdge,
cce3ef7e0162atagen2026-10-16 22:31 UTC309
    ) {
cce3ef7e0162atagen2026-10-16 22:31 UTC310
    }
cce3ef7e0162atagen2026-10-16 22:31 UTC311
cce3ef7e0162atagen2026-10-16 22:31 UTC312
    fn move_request(&mut self, _xwm: XwmId, _window: X11Surface, _button: u32) {}
cce3ef7e0162atagen2026-10-16 22:31 UTC313
cce3ef7e0162atagen2026-10-16 22:31 UTC314
    fn fullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) {
cce3ef7e0162atagen2026-10-16 22:31 UTC315
        self.x11_fullscreen(&window, true);
cce3ef7e0162atagen2026-10-16 22:31 UTC316
    }
cce3ef7e0162atagen2026-10-16 22:31 UTC317
cce3ef7e0162atagen2026-10-16 22:31 UTC318
    fn unfullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) {
cce3ef7e0162atagen2026-10-16 22:31 UTC319
        self.x11_fullscreen(&window, false);
cce3ef7e0162atagen2026-10-16 22:31 UTC320
    }
cce3ef7e0162atagen2026-10-16 22:31 UTC321
272bdf93aeadatagen2026-10-17 03:12 UTC322
    fn property_notify(&mut self, _xwm: XwmId, window: X11Surface, property: WmWindowProperty) {
272bdf93aeadatagen2026-10-17 03:12 UTC323
        if matches!(property, WmWindowProperty::Title | WmWindowProperty::Class)
272bdf93aeadatagen2026-10-17 03:12 UTC324
            && let Some(id) = self.x11_window_id(&window)
272bdf93aeadatagen2026-10-17 03:12 UTC325
        {
272bdf93aeadatagen2026-10-17 03:12 UTC326
            self.refresh_identity(id);
272bdf93aeadatagen2026-10-17 03:12 UTC327
        }
272bdf93aeadatagen2026-10-17 03:12 UTC328
    }
272bdf93aeadatagen2026-10-17 03:12 UTC329
cce3ef7e0162atagen2026-10-16 22:31 UTC330
    fn disconnected(&mut self, _xwm: XwmId) {
cce3ef7e0162atagen2026-10-16 22:31 UTC331
        tracing::info!("Xwayland exited");
cce3ef7e0162atagen2026-10-16 22:31 UTC332
        let gone: Vec<_> = self
a76842e12acaatagen2026-10-17 02:49 UTC333
            .wm
cce3ef7e0162atagen2026-10-16 22:31 UTC334
            .iter()
60956e3999deatagen2026-10-17 02:50 UTC335
            .filter(|(_, w)| w.window.x11_surface().is_some())
60956e3999deatagen2026-10-17 02:50 UTC336
            .map(|(id, _)| id)
cce3ef7e0162atagen2026-10-16 22:31 UTC337
            .collect();
cce3ef7e0162atagen2026-10-16 22:31 UTC338
        for id in gone {
cce3ef7e0162atagen2026-10-16 22:31 UTC339
            self.remove_window(id);
cce3ef7e0162atagen2026-10-16 22:31 UTC340
        }
c7e08bac3e01atagen2026-10-17 02:45 UTC341
        if let Some(x) = self.services.xwayland.as_mut() {
cce3ef7e0162atagen2026-10-16 22:31 UTC342
            x.wm = None;
cce3ef7e0162atagen2026-10-16 22:31 UTC343
            x.unmanaged.clear();
cce3ef7e0162atagen2026-10-16 22:31 UTC344
        }
cce3ef7e0162atagen2026-10-16 22:31 UTC345
        rearm(self);
cce3ef7e0162atagen2026-10-16 22:31 UTC346
    }
cce3ef7e0162atagen2026-10-16 22:31 UTC347
}
cce3ef7e0162atagen2026-10-16 22:31 UTC348
cce3ef7e0162atagen2026-10-16 22:31 UTC349
/// Dialogs, splash screens and fixed-size windows float at their own size.
cce3ef7e0162atagen2026-10-16 22:31 UTC350
fn floats(window: &X11Surface) -> bool {
cce3ef7e0162atagen2026-10-16 22:31 UTC351
    let kind = matches!(
cce3ef7e0162atagen2026-10-16 22:31 UTC352
        window.window_type(),
cce3ef7e0162atagen2026-10-16 22:31 UTC353
        Some(
cce3ef7e0162atagen2026-10-16 22:31 UTC354
            WmWindowType::Dialog
cce3ef7e0162atagen2026-10-16 22:31 UTC355
                | WmWindowType::Splash
cce3ef7e0162atagen2026-10-16 22:31 UTC356
                | WmWindowType::Utility
cce3ef7e0162atagen2026-10-16 22:31 UTC357
                | WmWindowType::Toolbar
cce3ef7e0162atagen2026-10-16 22:31 UTC358
                | WmWindowType::Menu
cce3ef7e0162atagen2026-10-16 22:31 UTC359
        )
cce3ef7e0162atagen2026-10-16 22:31 UTC360
    );
754ca2132943atagen2026-10-19 07:29 UTC361
    kind || fixed_size(window).is_some() || window.is_transient_for().is_some()
754ca2132943atagen2026-10-19 07:29 UTC362
}
754ca2132943atagen2026-10-19 07:29 UTC363
754ca2132943atagen2026-10-19 07:29 UTC364
/// The size of a window that asks for exactly one.
754ca2132943atagen2026-10-19 07:29 UTC365
pub fn fixed_size(window: &X11Surface) -> Option<Size<i32, Logical>> {
754ca2132943atagen2026-10-19 07:29 UTC366
    match (window.min_size(), window.max_size()) {
754ca2132943atagen2026-10-19 07:29 UTC367
        (Some(min), Some(max)) if min == max && min.w > 0 && min.h > 0 => Some(min),
754ca2132943atagen2026-10-19 07:29 UTC368
        _ => None,
754ca2132943atagen2026-10-19 07:29 UTC369
    }
cce3ef7e0162atagen2026-10-16 22:31 UTC370
}
cce3ef7e0162atagen2026-10-16 22:31 UTC371
cce3ef7e0162atagen2026-10-16 22:31 UTC372
impl State {
cce3ef7e0162atagen2026-10-16 22:31 UTC373
    pub fn x11_window_id(&self, window: &X11Surface) -> Option<monolith_layout::WindowId> {
a76842e12acaatagen2026-10-17 02:49 UTC374
        self.wm
cce3ef7e0162atagen2026-10-16 22:31 UTC375
            .iter()
60956e3999deatagen2026-10-17 02:50 UTC376
            .find(|(_, w)| w.window.x11_surface() == Some(window))
60956e3999deatagen2026-10-17 02:50 UTC377
            .map(|(id, _)| id)
cce3ef7e0162atagen2026-10-16 22:31 UTC378
    }
cce3ef7e0162atagen2026-10-16 22:31 UTC379
cce3ef7e0162atagen2026-10-16 22:31 UTC380
    fn forget_x11(&mut self, window: &X11Surface) {
c7e08bac3e01atagen2026-10-17 02:45 UTC381
        if let Some(x) = self.services.xwayland.as_mut() {
cce3ef7e0162atagen2026-10-16 22:31 UTC382
            x.unmanaged.retain(|w| w != window);
cce3ef7e0162atagen2026-10-16 22:31 UTC383
        }
cce3ef7e0162atagen2026-10-16 22:31 UTC384
        if let Some(id) = self.x11_window_id(window) {
cce3ef7e0162atagen2026-10-16 22:31 UTC385
            self.remove_window(id);
cce3ef7e0162atagen2026-10-16 22:31 UTC386
        }
7e58494265dbatagen2026-10-17 02:56 UTC387
        self.dirty.redraw = true;
cce3ef7e0162atagen2026-10-16 22:31 UTC388
    }
cce3ef7e0162atagen2026-10-16 22:31 UTC389
cce3ef7e0162atagen2026-10-16 22:31 UTC390
    fn x11_fullscreen(&mut self, window: &X11Surface, on: bool) {
cce3ef7e0162atagen2026-10-16 22:31 UTC391
        if let Some(id) = self.x11_window_id(window)
a76842e12acaatagen2026-10-17 02:49 UTC392
            && self
a76842e12acaatagen2026-10-17 02:49 UTC393
                .wm
a76842e12acaatagen2026-10-17 02:49 UTC394
                .layout
a76842e12acaatagen2026-10-17 02:49 UTC395
                .window(id)
a76842e12acaatagen2026-10-17 02:49 UTC396
                .is_some_and(|w| w.fullscreen != on)
cce3ef7e0162atagen2026-10-16 22:31 UTC397
        {
a76842e12acaatagen2026-10-17 02:49 UTC398
            self.wm.layout.toggle_fullscreen(id);
f50ebc6999a6atagen2026-10-19 07:51 UTC399
            self.wm.layout.focus_if_covering(id);
cce3ef7e0162atagen2026-10-16 22:31 UTC400
            self.apply_layout();
cce3ef7e0162atagen2026-10-16 22:31 UTC401
        }
cce3ef7e0162atagen2026-10-16 22:31 UTC402
    }
cce3ef7e0162atagen2026-10-16 22:31 UTC403
}
0e6ab4933125atagen2026-10-17 05:08 UTC404
0e6ab4933125atagen2026-10-17 05:08 UTC405
/// X11 requests fail when the window is already gone, which is routine, but
0e6ab4933125atagen2026-10-17 05:08 UTC406
/// a failure on a live window means it never got the geometry we chose.
0e6ab4933125atagen2026-10-17 05:08 UTC407
pub fn logged<E: std::fmt::Display>(what: &str, result: Result<(), E>) {
0e6ab4933125atagen2026-10-17 05:08 UTC408
    if let Err(e) = result {
0e6ab4933125atagen2026-10-17 05:08 UTC409
        tracing::debug!("X11 {what} failed: {e}");
0e6ab4933125atagen2026-10-17 05:08 UTC410
    }
0e6ab4933125atagen2026-10-17 05:08 UTC411
}
28e12a7e36cdatagen2026-10-17 05:11 UTC412
28e12a7e36cdatagen2026-10-17 05:11 UTC413
/// Whether `pid` is alive and could own an X display lock. A SIGKILLed
28e12a7e36cdatagen2026-10-17 05:11 UTC414
/// monolith leaves its lock behind, and its pid may since belong to anything.
28e12a7e36cdatagen2026-10-17 05:11 UTC415
fn holds_display(pid: u32) -> bool {
28e12a7e36cdatagen2026-10-17 05:11 UTC416
    std::fs::read_to_string(format!("/proc/{pid}/comm"))
28e12a7e36cdatagen2026-10-17 05:11 UTC417
        .is_ok_and(|comm| comm.starts_with('X') || comm.trim() == "monolith")
28e12a7e36cdatagen2026-10-17 05:11 UTC418
}