From f436f53f0d3a71431596bd620fbd20b349ab8711 Mon Sep 17 00:00:00 2001 From: Mariano Abad Date: Fri, 7 Aug 2026 19:29:04 -0300 Subject: [PATCH] fix: only fall back when nothing named an endpoint, and validate the directory Review of the first version, all three correct. The important one: `connect_to_env()` honours `WAYLAND_DISPLAY` and `WAYLAND_SOCKET`, and the fallback ignored that choice. If an explicit endpoint was named and failed, this could attach to a *different* compositor and read output positions from the wrong display. It now runs only when neither variable is set, which is also the only case it was ever justified by. `XDG_RUNTIME_DIR` is read with `var_os` and rejected when relative, since `.` or `../tmp` would probe against the working directory. And every failed probe is kept instead of the last one overwriting the first, so an error naming both sockets says what happened to both. The comment is three lines now: the reasoning belongs here, in the commit. --- src/platform/linux.rs | 59 ++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/src/platform/linux.rs b/src/platform/linux.rs index 86abf9fa4..4086db473 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -1,4 +1,4 @@ -use crate::ResultType; +use crate::{bail, ResultType}; use std::{ collections::HashMap, path::{Path, PathBuf}, @@ -377,45 +377,46 @@ pub struct WaylandDisplayInfo { } // Retrieves information about all connected displays via the Wayland protocol. -/// Connect to the compositor socket sitting in `XDG_RUNTIME_DIR` when `WAYLAND_DISPLAY` is not set. -/// -/// A login screen is the case this exists for. The greeter's `--server` is started deliberately -/// without the compositor variables, because the DRM capture path talks to the root service and a -/// render node and never to the compositor -- but the compositor IS running, and its socket is -/// owned by the very user this process runs as. Measured at an sddm greeter: `/run/user/112/wayland-0` -/// exists, a plain `connect(2)` as that user succeeds, and the registry advertises `wl_output`. -/// -/// Without this the output layout is unavailable at a login screen, and everything downstream has -/// to guess: every DRM display reports origin (0,0) because on Wayland each output scans out of its -/// own framebuffer, so the uinput pointer range collapsed to a single display on a multi-monitor -/// greeter. -/// -/// Strictly a fallback after `connect_to_env` has already failed, so it cannot change any host -/// where that succeeds; and where there is no compositor at all the socket is simply absent and -/// this fails exactly as before. +// A greeter's `--server` is started without the compositor variables, so nothing tells +// the enumerator where a compositor that IS running lives. Only when nothing was told: +// an explicit endpoint that fails must not silently reattach to a different compositor. #[cfg(target_os = "linux")] fn connect_to_runtime_dir_socket() -> ResultType { use std::os::unix::net::UnixStream; - let dir = std::env::var("XDG_RUNTIME_DIR")?; - let mut last = None; - // Compositors name the socket `wayland-N`; 0 and 1 cover a greeter and a session started after - // it. Anything beyond that is not worth probing blind. + if std::env::var_os("WAYLAND_DISPLAY").is_some() || std::env::var_os("WAYLAND_SOCKET").is_some() + { + bail!("an explicit wayland endpoint is set and did not connect"); + } + let dir = match std::env::var_os("XDG_RUNTIME_DIR") { + Some(dir) => std::path::PathBuf::from(dir), + None => bail!("XDG_RUNTIME_DIR is not set"), + }; + // Relative would probe against the working directory, not the runtime directory. + if !dir.is_absolute() { + bail!("XDG_RUNTIME_DIR is not absolute: {}", dir.display()); + } + let mut errs = Vec::new(); for name in ["wayland-0", "wayland-1"] { - let path = std::path::Path::new(&dir).join(name); + let path = dir.join(name); if !path.exists() { continue; } - match UnixStream::connect(&path).map_err(anyhow::Error::from).and_then(|s| { - Connection::from_socket(s).map_err(anyhow::Error::from) - }) { + match UnixStream::connect(&path) + .map_err(anyhow::Error::from) + .and_then(|s| Connection::from_socket(s).map_err(anyhow::Error::from)) + { Ok(conn) => return Ok(conn), - Err(err) => last = Some(format!("{}: {err}", path.display())), + Err(err) => errs.push(format!("{}: {err}", path.display())), } } - Err(anyhow::anyhow!( + bail!( "no usable wayland socket in XDG_RUNTIME_DIR ({})", - last.unwrap_or_else(|| "none present".to_owned()) - )) + if errs.is_empty() { + "none present".to_owned() + } else { + errs.join("; ") + } + ) } pub fn get_wayland_displays() -> ResultType> {