Skip to content

Fix tray menu desync and Steam lightbar conflicts; gate v2 revokes pre-gate fds#3

Merged
NicolasPogorzelski merged 5 commits into
mainfrom
fix/menu-id-churn-and-steam-gate
Jul 5, 2026
Merged

Fix tray menu desync and Steam lightbar conflicts; gate v2 revokes pre-gate fds#3
NicolasPogorzelski merged 5 commits into
mainfrom
fix/menu-id-churn-and-steam-gate

Conversation

@NicolasPogorzelski

Copy link
Copy Markdown
Owner

Fixes #2.

Field bugs and verified root causes

  1. Tray menu items stuck disabled / clicks dead after reconnect churn (Tray menu clicks stop reaching the daemon after reconnect churn (Plasma SNI/dbusmenu) #2).
    Verified live: the daemon served enabled=true over D-Bus while the GNOME
    appindicator host (desktop is GNOME, not Plasma as the issue hypothesised)
    rendered the same item disabled. The host caches item properties per
    dbusmenu id and misses reused ids across structural changes
    (separator→radio after a connect). Fix: cached menu model, never-recycled
    ids, ItemsPropertiesUpdated deltas. Decision: docs/decisions/tray-menu-model.md.

  2. DualSense lightbar dark despite correct kernel LED state. Steam Input
    opens every pad's hidraw node on connect; the fd survives the chmod gate
    and Steam firmware-latches the lightbar off ('light out' setup flag) —
    kernel LED writes then change nothing until re-probe. Fix: gate v2 —
    identity-keyed markers, udev-born-gated nodes (72- rule; must outrank the
    71-*-controllers rules from steam-devices, field-verified), driver rebind
    to revoke fds and reset the latch. Decision: docs/decisions/hidraw-gate.md.

  3. Resting colour policy (ps5-native). Holder-set watching: games own the
    lightbar while they hold the pad; releasers trigger rearm + repaint;
    Steam's post-rebind reopen (it enumerates even with PlayStation support
    disabled and writes defaults once) is outlasted by a delayed repaint.
    Decision: docs/decisions/steam-coexistence.md.

Also: Sunshine/inputtino virtual pads excluded from the scan, identical
uniq-less pads kept apart via phys, remapper liveness re-assert, unit tests
for the menu id model and new reconcile branches (50 assertions).

Verification on hardware

  • Tray fully functional across power-cycles and full unpair/repair
  • Xbox emulation stays green with Steam running; no double input
    (Steam + Lutris/Proton titles)
  • Resting blue returns automatically after game exit (journal + holder logs)
  • Games with native DualSense support (Stellar Blade) get the raw pad in
    ps5-native; titles without it use the Xbox mode as intended

…ted udev nodes

chmod alone cannot gate a pad an application already holds: Steam Input
opens every controller's hidraw node on connect and its surviving fd kept
reading the pad (double input) and writing output reports (lightbar
firmware-latched dark, immune to kernel LED writes until re-probe).

Gate v2 keys the gate by HID_UNIQ instead of a volatile node:
- marker files under /run/controller-manager/gated (tmpfs: reboots fail-open)
- 71-controller-manager.rules: gated hidraw nodes are BORN with MODE 0000
  and without the uaccess tag (placed between 70-uaccess and 73-seat-late,
  otherwise the seat ACL would tunnel through MODE 0000)
- driver unbind/bind on gate transitions revokes every open fd and resets
  the DualSense lightbar latch; strictly transition-only to avoid rebind
  loops with the daemon's re-asserts; 'rearm' rebinds without gating
- block-node/restore-node keep the v1 chmod path for uniq-less pads
…+ resting-blue integration

Menu (Issue #2, verified live on GNOME): the appindicator host caches item
properties per dbusmenu id and misses reused ids across structural changes
(separator->radio after a connect), leaving entries stuck disabled while the
daemon serves enabled=true. The model is now built once per state change and
cached (GetLayout/GetGroupProperties/GetProperty/Event answer from one
snapshot, so clicks can no longer be misrouted by a shifting id map);
structural changes allocate fresh ids from a monotonic counter (Quit keeps
its constant id), display-only changes keep ids and go out as
ItemsPropertiesUpdated deltas.

Manager side of gate v2: gate before grabbing, re-resolve the renumbered
nodes afterwards (_refresh_nodes), stay gated across reconnects (rebind no
longer restores), and in ps5-native watch the pad's hidraw holders — when
the last one exits, rearm the pad (lightbar latch) and repaint the resting
blue. Scan now ignores Sunshine/inputtino virtual pads (synthetic phys /
'(virtual)' names), identical uniq-less pads are kept apart via phys, and a
dead remapper thread on a present pad re-asserts its mode (sub-poll BT
blips). Unit tests cover the id model and the new reconcile branches.
… MODE assignment

Field-verified leak: steam-devices ships 71-sony-controllers.rules, which
runs after 71-controller-manager.rules (alphabetical within the same
number) and re-adds MODE 0660 + the uaccess tag for DualSense hidraw. The
reborn node came back 0660 with a seat ACL, Steam/games reopened it in a
gated mode and repainted the lightbar (brief green, then stomped).

- rename the rule to 72-: after every 71-*-controllers rule, still before
  73-seat-late turns the uaccess tag into the seat ACL
- MODE:=/GROUP:= final assignments so no later rule can override them
  (TAG-= has no final form; the 72- ordering is what protects it)
- helper re-chmods the reborn nodes after the rebind as belt and braces
  (also zeroes the ACL mask should an ACL ever leak through again)
- install.sh migrates: installs 72-, removes a stale 71- from earlier
  installs, reloads udev
Field logs: switching to ps5-native rebinds the driver (gate restore); ~6 s
later Steam reopens the reborn hidraw node — it enumerates pads even with
PlayStation support disabled — and writes its defaults ONCE, stomping the
just-painted resting blue. A second click on the already-active mode
'fixed' it because that path rebinds nothing, so nobody reopened and the
repaint kept the last word.

watch_holders now classifies holder-set deltas instead of a boolean:
releasers (game exit) rearm + repaint as before; new holders inside a 20 s
settle window after a gate transition are reopen noise and get outlasted
by one delayed repaint; new holders outside the window are a deliberate
game start — hands off, pending repaints cancelled. apply_mode opens the
settle window after every gate transition.
@NicolasPogorzelski NicolasPogorzelski merged commit 02ee078 into main Jul 5, 2026
2 checks passed
@NicolasPogorzelski NicolasPogorzelski deleted the fix/menu-id-churn-and-steam-gate branch July 5, 2026 14:20
NicolasPogorzelski added a commit that referenced this pull request Jul 5, 2026
The overview still described the v1 chmod-only gate (single helper, 'restores
to 666') and a menu 'rebuilt on every change'; the verify runbook checked the
pre-switch hidraw node, which gate v2's driver rebind renumbers, and expected
hard-coded 666 permissions where distribution rules now set the open mode +
seat ACL. Both now match the shipped design: identity-keyed gate (marker +
udev-born-gated nodes + rebind), resting-colour policy, cached menu model
with never-recycled ids, phys identity fallback and the Sunshine exclusion.
Roadmap: 2026-07-05 fixes are hardware-verified and merged (PR #3).
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.

Tray menu clicks stop reaching the daemon after reconnect churn (Plasma SNI/dbusmenu)

1 participant