fix(inject/mutter): GNOME input via Mutter's direct EIS, not the xdg portal
ci / rust (push) Successful in 56s
ci / web (push) Failing after 35s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 3s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
ci / docs-site (push) Failing after 38s
apple / swift (push) Successful in 1m15s

On a headless GNOME host the xdg-desktop-portal RemoteDesktop Start() blocks on an
interactive "Allow remote control?" approval nobody can click, so libei input timed out
("EIS setup timed out") and neither mouse nor keyboard worked — even though video worked
(it uses Mutter's direct RemoteDesktop API).

Add EiSource::MutterEis: obtain the EIS fd from
org.gnome.Mutter.RemoteDesktop.Session.ConnectToEIS (CreateSession → Start → ConnectToEIS),
no portal and no approval. Selected for GNOME/Mutter; KWin keeps the RemoteDesktop portal,
gamescope keeps its own EIS socket.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-12 12:45:10 +00:00
parent 94552331ef
commit 0c4cfa40be
2 changed files with 88 additions and 10 deletions
+22 -1
View File
@@ -48,7 +48,9 @@ pub fn open(backend: Backend) -> Result<Box<dyn InputInjector>> {
Backend::Libei => {
#[cfg(target_os = "linux")]
{
Ok(Box::new(libei::LibeiInjector::open()?))
Ok(Box::new(
libei::LibeiInjector::open_with(libei_ei_source())?,
))
}
#[cfg(not(target_os = "linux"))]
{
@@ -105,6 +107,25 @@ pub fn default_backend() -> Backend {
}
}
/// How the libei backend reaches its EIS server. KWin goes through the `RemoteDesktop` *portal*
/// (with a pre-seeded grant), but GNOME's portal `Start()` needs an interactive approval a
/// headless host can't answer — so GNOME goes straight to Mutter's *direct* RemoteDesktop EIS
/// (`org.gnome.Mutter.RemoteDesktop`), the same direct API the Mutter video backend uses.
#[cfg(target_os = "linux")]
fn libei_ei_source() -> libei::EiSource {
let gnome = std::env::var("PUNKTFUNK_COMPOSITOR")
.is_ok_and(|v| v.trim().eq_ignore_ascii_case("mutter"))
|| std::env::var("XDG_CURRENT_DESKTOP")
.unwrap_or_default()
.to_ascii_uppercase()
.contains("GNOME");
if gnome {
libei::EiSource::MutterEis
} else {
libei::EiSource::Portal
}
}
/// Map a Windows Virtual-Key code (as sent by Moonlight/GameStream) to a Linux evdev key code.
pub fn vk_to_evdev(vk: u8) -> Option<u16> {
match vk {