Skip to content

Fix input-capture session-recreate churn and lockfile-race crash on non-GNOME compositors - #66

Draft
peteonrails wants to merge 2 commits into
jondkinney:mainfrom
peteonrails:fix/input-capture-session-recreate-cooldown
Draft

Fix input-capture session-recreate churn and lockfile-race crash on non-GNOME compositors#66
peteonrails wants to merge 2 commits into
jondkinney:mainfrom
peteonrails:fix/input-capture-session-recreate-cooldown

Conversation

@peteonrails

Copy link
Copy Markdown

Summary

Two related fixes to the libei capture backend for how it handles the
GNOME/mutter session-recreate workaround on other compositors
(observed on Hyprland 0.56.0). Both are scoped to the non-GNOME path;
GNOME behaviour is unchanged.

The problem

input-capture/src/libei.rs recreates the whole portal + EIS session
whenever EIS reports a seat/device change. That exists purely to work
around an xdg-desktop-portal-gnome / mutter bug ("no events after a
disable"), but it ran on every compositor.

On Hyprland the captured-keyboard device is added and removed as normal
EIS lifecycle, so this fired continuously and unattended — several
recreations per second at times, each a full portal session teardown +
connect_to_eis rebuild. Two concrete failure modes:

  1. Compositor abort (reproduced). When recreations bunch up, a new
    EIS backend tries to grab /run/user/<uid>/eis-N.lock before the
    previous connection's teardown has released it. libei handles that
    lost race with a fatal assertion (eis_backend_fd_add_client: Assertion 'eis->backend' failed) and abort()s. Because Hyprland
    embeds libei as its EIS implementation, that takes down the whole
    compositor, not just the capture session.

  2. Pointless churn the rest of the time: constant portal spam and
    CPU with no user activity.

The fixes

  • Cooldown (SESSION_RECREATE_COOLDOWN, 300ms): if a session was
    just closed, wait out the remainder before opening a new one, so the
    previous EIS backend can release its lockfile. Closes the race in (1).
    No effect on the common case (a recreate more than 300ms after the
    last close waits zero).

  • Gate the workaround to GNOME/mutter (XDG_CURRENT_DESKTOP): on
    other compositors a device coming or going is normal EIS lifecycle,
    not a reason to rebuild the session — so keep the session and just
    log it. Eliminates the churn in (2) at the source.

Testing

  • cargo build, cargo clippy --all-targets, cargo fmt --check
    clean on the touched crate.
  • Ran the patched daemon on Hyprland 0.56.0 (dual monitor, KVM
    handover). Confirmed via debug logs that the gate suppresses the
    recreate on DeviceRemoved("captured keyboard") (session recreations
    drop to zero) and that the cooldown engages when a recreate does
    follow a recent close (waiting 299.9…ms before opening a new EIS session), after which the session comes back cleanly.

Scope / what this does NOT fix

While debugging on Hyprland I also hit a separate compositor-side fd
leak
: Hyprland does not reclaim the dup'd keymap fd when it cycles its
EIS "captured keyboard" device, so under heavy active-keyboard churn
(here: a dictation tool injecting via ydotool) its fd table fills and it
eventually aborts on a NULL keymap. That is a Hyprland/libei bug and is
not addressed here — these two changes do not stop it (the device
cycle continues within a single session). Filing it upstream separately.
I mention it only so the scope of this PR is clear: it fixes the
session-recreate race and churn, not the fd leak.

The GNOME/mutter workaround recreates the whole input-capture
session (fresh portal session + fresh EIS connection) on every
SeatRemoved/DeviceRemoved event, unconditionally on every
compositor. In practice this fires on effectively every capture
handover, not just rare cases.

On Hyprland 0.56.0, recreating fast enough can race the previous
EIS backend's release of /run/user/<uid>/eis-N.lock: the new
backend fails to acquire its lock, and libei's
eis_backend_fd_add_client hits a fatal assertion and aborts.
Because Hyprland embeds libei as its own EIS implementation, that
abort takes down the entire compositor, not just this capture
session.

Add a 300ms cooldown before opening a new session if one was just
closed, giving the old backend time to finish tearing down. No
behavior change in the common case (recreation happening more than
300ms after the last close costs nothing).
The session-recreate-on-device-change behaviour exists solely to work
around an xdg-desktop-portal-gnome / mutter bug (no events after a
disable). It was applied on every compositor, including wlroots-based
ones like Hyprland, which routinely remove and re-add the captured
keyboard device as normal EIS lifecycle.

On Hyprland that turned every DeviceRemoved into a full portal + EIS
session teardown and rebuild, firing continuously and unattended —
wasteful portal churn, and when recreations bunch up it trips the
lockfile-acquisition race the cooldown guards against. Gate the
recreate to GNOME (via XDG_CURRENT_DESKTOP); elsewhere, treat a device
coming or going as normal and keep the session.
@peteonrails
peteonrails force-pushed the fix/input-capture-session-recreate-cooldown branch from a5e44e0 to 0847086 Compare July 25, 2026 03:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant