Fix input-capture session-recreate churn and lockfile-race crash on non-GNOME compositors - #66
Draft
peteonrails wants to merge 2 commits into
Draft
Conversation
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
force-pushed
the
fix/input-capture-session-recreate-cooldown
branch
from
July 25, 2026 03:28
a5e44e0 to
0847086
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.rsrecreates the whole portal + EIS sessionwhenever 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_eisrebuild. Two concrete failure modes:Compositor abort (reproduced). When recreations bunch up, a new
EIS backend tries to grab
/run/user/<uid>/eis-N.lockbefore theprevious connection's teardown has released it. libei handles that
lost race with a fatal assertion (
eis_backend_fd_add_client: Assertion 'eis->backend' failed) andabort()s. Because Hyprlandembeds libei as its EIS implementation, that takes down the whole
compositor, not just the capture session.
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 wasjust 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): onother 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 --checkclean on the touched crate.
handover). Confirmed via debug logs that the gate suppresses the
recreate on
DeviceRemoved("captured keyboard")(session recreationsdrop 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.