From 4dcb10d9f1894ef78b4780b66bce3c738cc0ce36 Mon Sep 17 00:00:00 2001 From: Stephen Hellings Date: Mon, 9 Mar 2026 11:21:08 -0400 Subject: [PATCH] Attempt 3 to fix launching permission denied --- config.example.toml | 4 ++-- src/config.rs | 26 ++++++++++++++++++++---- src/main.rs | 48 ++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 69 insertions(+), 9 deletions(-) diff --git a/config.example.toml b/config.example.toml index f312d72..0a0eb33 100644 --- a/config.example.toml +++ b/config.example.toml @@ -1,10 +1,10 @@ [compositor] socket_name = "wo-0" -ipc_socket = "/run/user/1000/wo-ipc.sock" +ipc_socket = "$XDG_RUNTIME_DIR/wo-ipc.sock" electron_path = "electron" background = [0.08, 0.10, 0.12, 1.0] enable_portal = true -portal_socket = "/run/user/1000/wo-portal.sock" +portal_socket = "$XDG_RUNTIME_DIR/wo-portal.sock" enable_syscalls = true focus_notify = true diff --git a/src/config.rs b/src/config.rs index d9b86e3..3b3db92 100644 --- a/src/config.rs +++ b/src/config.rs @@ -246,10 +246,17 @@ impl ApplicationConfig { // Defaults fn default_socket_name() -> String { "wo-0".into() } -fn default_ipc_socket() -> String { "/run/user/1000/wo-ipc.sock".into() } fn default_electron_path() -> String { "electron".into() } fn default_background() -> [f32; 4] { [0.1, 0.1, 0.1, 1.0] } -fn default_portal_socket() -> String { "/run/user/1000/wo-portal.sock".into() } + +/// Returns $XDG_RUNTIME_DIR, falling back to /run/user/. +fn runtime_dir() -> String { + std::env::var("XDG_RUNTIME_DIR") + .unwrap_or_else(|_| format!("/run/user/{}", unsafe { libc::getuid() })) +} + +fn default_ipc_socket() -> String { format!("{}/wo-ipc.sock", runtime_dir()) } +fn default_portal_socket() -> String { format!("{}/wo-portal.sock", runtime_dir()) } fn default_width() -> u32 { 1920 } fn default_height() -> u32 { 1080 } fn default_nested_width() -> u32 { 1920 } @@ -259,6 +266,12 @@ fn default_format() -> String { "ARGB8888".into() } fn default_asset_prefix() -> String { "file".into() } fn default_true() -> bool { true } +fn expand_path(s: &str) -> String { + shellexpand::full(s) + .map(|cow| cow.into_owned()) + .unwrap_or_else(|_| s.to_owned()) +} + impl Config { pub fn load() -> Result { // Try multiple locations in order of priority: @@ -278,8 +291,13 @@ impl Config { let text = std::fs::read_to_string(&path_opt) .with_context(|| format!("reading config {path_opt:?}"))?; - return toml::from_str(&text) - .with_context(|| format!("parsing config {path_opt:?}")); + let mut config: Self = toml::from_str(&text) + .with_context(|| format!("parsing config {path_opt:?}"))?; + config.compositor.ipc_socket = + expand_path(&config.compositor.ipc_socket); + config.compositor.portal_socket = + expand_path(&config.compositor.portal_socket); + return Ok(config); } } diff --git a/src/main.rs b/src/main.rs index 5e8342e..465d077 100644 --- a/src/main.rs +++ b/src/main.rs @@ -778,10 +778,52 @@ fn run_drm(config: Config) -> Result<(), anyhow::Error> { let drm_device_fd = DrmDeviceFd::new(DeviceFd::from(drm_fd)); - if let Err(e) = drm::Device::acquire_master_lock(&drm_device_fd) { + // Retry acquiring DRM master: on logind-managed sessions (e.g. Ly) the VT + // switch and master hand-off from logind may not be complete at the exact + // moment session.open() returns. libseat's builtin backend logs "Unable to + // become DRM master, entering unprivileged mode" and returns the fd anyway; + // pumping the event loop lets session-activation events propagate so that + // the next drmSetMaster() attempt succeeds. + const DRM_MASTER_RETRY_MS: u64 = 100; + const DRM_MASTER_RETRY_ROUNDS: u32 = 30; // up to ~3 s + let mut last_master_err = None; + for round in 0..=DRM_MASTER_RETRY_ROUNDS { + match drm::Device::acquire_master_lock(&drm_device_fd) { + Ok(()) => { + if round > 0 { + info!("DRM master acquired after {} retries", round); + } + last_master_err = None; + break; + } + Err(e) => { + let detail = e.to_string(); + if round == 0 { + warn!( + gpu = %gpu_path.display(), + "DRM master not yet available ({}); retrying up to {}×{}ms...", + detail, + DRM_MASTER_RETRY_ROUNDS, + DRM_MASTER_RETRY_MS, + ); + } + last_master_err = Some(detail); + let _ = event_loop.dispatch( + Some(Duration::from_millis(DRM_MASTER_RETRY_MS)), + &mut pre_init, + ); + } + } + } + if let Some(e) = last_master_err { return Err(anyhow::anyhow!( - "failed to become DRM master on {}: {e}. The GPU is likely in use by another active session/compositor. Switch to a free VT, stop the other session, or set compositor.drm_device to a free card.", - gpu_path.display() + "failed to become DRM master on {gpu}: {e}. \ + The GPU is likely in use by another active session/compositor. \ + Ensure wo is launched as the session compositor by a logind-aware \ + display manager (e.g. Ly, greetd) with pam_systemd.so in its PAM \ + config, or switch to a free VT first. \ + If using seatd, add your user to the 'seat' group.", + gpu = gpu_path.display(), )); }