From 8107a9173cf5fc8eeb4972280c276f85f0702708 Mon Sep 17 00:00:00 2001 From: Kyle Gospodnetich Date: Tue, 11 Aug 2026 23:09:04 -0700 Subject: [PATCH] fix: Correct display detection issue that causes OGUI to fail to render on GNOME desktop --- extensions/core/src/gamescope.rs | 71 +++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/extensions/core/src/gamescope.rs b/extensions/core/src/gamescope.rs index a02b41a2..fa177b09 100644 --- a/extensions/core/src/gamescope.rs +++ b/extensions/core/src/gamescope.rs @@ -1,13 +1,23 @@ pub mod x11_client; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::env; +use std::sync::mpsc; +use std::thread; +use std::time::{Duration, Instant}; use x11_client::GamescopeXWayland; +use gamescope_x11_client::atoms::GamescopeAtom; +use gamescope_x11_client::xwayland::XWayland; + use godot::prelude::*; use godot::classes::{Engine, Resource}; +/// How long to wait for an X11 display to connect +/// before giving up on it. +const DISPLAY_PROBE_TIMEOUT: Duration = Duration::from_secs(2); + #[derive(GodotClass)] #[class(base=Resource)] pub struct GamescopeInstance { @@ -108,7 +118,7 @@ impl IResource for GamescopeInstance { } // Discover any gamescope instances - let result = gamescope_x11_client::discover_gamescope_displays(); + let result = discover_gamescope_displays(); let x11_displays = match result { Ok(displays) => displays, Err(e) => { @@ -160,3 +170,60 @@ impl IResource for GamescopeInstance { } } } + +/// Returns the names of every X11 display that is a Gamescope XWayland +fn discover_gamescope_displays() -> Result, Box> { + let mut seen = HashSet::new(); + let displays = gamescope_x11_client::discover_x11_displays()? + .into_iter() + .filter(|display| seen.insert(display.clone())); + + let probes: Vec<(String, mpsc::Receiver)> = displays + .map(|display| { + let (tx, rx) = mpsc::sync_channel(1); + let name = display.clone(); + thread::spawn(move || { + let _ = tx.try_send(is_gamescope_display(&name)); + }); + (display, rx) + }) + .collect(); + + let deadline = Instant::now() + DISPLAY_PROBE_TIMEOUT; + let mut gamescope_displays = Vec::new(); + for (display, rx) in probes { + let remaining = deadline.saturating_duration_since(Instant::now()); + match rx.recv_timeout(remaining) { + Ok(true) => gamescope_displays.push(display), + Ok(false) => (), + Err(_) => log::warn!("Display {display} did not respond in time; skipping it"), + } + } + + Ok(gamescope_displays) +} + +/// Connects to the given display and reports whether it is a Gamescope XWayland. +fn is_gamescope_display(display: &str) -> bool { + let mut xwayland = XWayland::new(display.to_string()); + if let Err(e) = xwayland.connect() { + log::debug!("Failed to connect to display {display}: {e:?}"); + return false; + } + + let root_window_id = match xwayland.get_root_window_id() { + Ok(root_window_id) => root_window_id, + Err(e) => { + log::debug!("Failed to get root window for display {display}: {e:?}"); + return false; + } + }; + + match xwayland.has_xprop(root_window_id, GamescopeAtom::XwaylandServerId) { + Ok(is_gamescope) => is_gamescope, + Err(e) => { + log::debug!("Failed to query display {display}: {e:?}"); + false + } + } +}