Skip to content

Focus ring fixes for Steam - #2301

Merged
matte-schwartz merged 6 commits into
ValveSoftware:masterfrom
matte-schwartz:focus-ring-fixes
Aug 19, 2026
Merged

Focus ring fixes for Steam#2301
matte-schwartz merged 6 commits into
ValveSoftware:masterfrom
matte-schwartz:focus-ring-fixes

Conversation

@matte-schwartz

@matte-schwartz matte-schwartz commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

At startup, the breathing focus ring on the Steam home screen sometimes doesn't appear, and keyboard input goes nowhere. Flipping to a dialog and back fixes it. Whether it happens depends on startup timing, which made it unbisectable.

After adding a focus_info debug command, it showed that Steam's CEF takes input on a subwindow inside the toplevel, and focuses it with XSetInputFocus(RevertToParent). Since b471a05, gamescope tracks that subwindow so no-op focus rerolls don't yank focus away from it, but the XSetInputFocus call itself still targeted the toplevel. When the focus apply path re-fires during startup churn after CEF has focused its subwindow, focus moves back to the toplevel. CEF sees FocusOut on its browser window, considers the context unfocused, and stops drawing the ring. Gamescope's bookkeeping already records the subwindow as focused, so it never re-applies, and the state sticks until something forces a new focus change.

Broken state info dump:

[gamescope] [Info]  focus: Server 0 keyboard focus: 0x1e00035 (Steam Big Picture Mode) revert=0 wanted=0x1400006
[gamescope] [Info]  focus:   Focus window: 0x1e00035 (Steam Big Picture Mode) appID=769
[gamescope] [Info]  focus:   Input focus window: 0x1e00035 (Steam Big Picture Mode) appID=769
[gamescope] [Info]  focus:   Override window: none

Working state with focus ring dump:

[gamescope] [Info]  focus: Server 0 keyboard focus: 0x1400006 (Steam Big Picture Mode) revert=2 wanted=0x1400006
[gamescope] [Info]  focus:   Focus window: 0x1e00035 (Steam Big Picture Mode) appID=769
[gamescope] [Info]  focus:   Input focus window: 0x1e00035 (Steam Big Picture Mode) appID=769
[gamescope] [Info]  focus:   Override window: none

To repro the original bug, the easiest way to do so is just continuously run a loop of gamescope -e --xwayland-count 2 -- steam -steamdeck -steamos3 -steampal -gamepadui and then if the focus ring appears after Steam loads, kill gamescope and run the command again. Usually within 5-10 tries you can hit this bug, but I've had it take up to 15 tries before. This series has survived 75 restarts here so far with nested gamescope, I'll verify on DRM backend tomorrow.

@matte-schwartz
matte-schwartz requested a review from oSoMoN August 13, 2026 01:21
Dumps the global and per-server focus state, including where the X
server actually has keyboard focus versus the window gamescope wanted
it on, and the focused app and window properties Steam reads back.

Only the current connector's focus gets published to Steam, so a
correct focus that never made it into the properties is invisible in
a dump of the properties alone. Print each connector's focus next to
the current key.
b471a05 keeps track of a focused subwindow so no-op focus rerolls
don't yank keyboard focus away from it, but the XSetInputFocus call
still targeted the toplevel. When the apply path re-fires after Steam's
CEF has focused its browser subwindow, focus moves back to the toplevel
and Steam loses its focus ring until something refocuses it.

Target the preserved window instead, and let subwindow focus revert to
its parent so a destroyed subwindow can't strand focus on None.
A client can drop keyboard focus to None, and with RevertToNone any
revert also lands there. The FocusOut handler only corrected focus when
it moved to another tracked window, and its guard never matched while
focus was preserved on a subwindow.

Match the stored window directly, track focus moves within the same
toplevel, and take focus back when it went to None.
XGetWindowProperty hands back an allocation even when the property
holds zero elements, so get_prop returned uninitialized memory for
them instead of the default. The focused app and window atoms are
published with zero elements whenever there is no focus.
determine_and_apply_focus publishes the focused app from the input
focus window, but a global focus can hold a focus window while the
input focus window is null. The dereference crashes steamcompmgr.

Only read the appID when an input focus window exists. The focused
app then stays zero and the atom is published with no elements.
The keyboard and mouse focus connectors normally follow SteamVR's
overlay input focus grants, but SteamVR can park input focus on
nothing while a launching app's overlay flickers through hide and
show, and nothing regrants it until a pointer interaction. The stale
connectors keep steamcompmgr publishing the previous app's focused app
atoms, skip the wlserver input focus handoff, and starve mangoapp of
nudges, so MangoHud sits frozen until the first click.

Take keyboard and mouse focus when our connector becomes visible
instead of waiting for the grant. Defer when the grant currently
points at the holding connector's own overlay, so becoming visible
cannot steal focus SteamVR deliberately gave another connector.
Steam's per-app controller activation still follows SteamVR's own
grant, so this does not cure the dead first launch, it keeps
gamescope's focus state correct through it.
@matte-schwartz

Copy link
Copy Markdown
Collaborator Author

added some additional fixes found while working with the openvr backend, so we'll want to cherry-pick this to the steamframe branch as well. main fix for openvr is the mangohud overlay now properly drawing on a game after first launch, where previously it wouldn't until first interacting with the game.

@oSoMoN

oSoMoN commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

The new focus_info command is really nice, thanks for it!

One problem that this patchset doesn't address is the focus ring disappearing after opening then closing the Power menu (on a Steam Deck: Steam button to bring the Steam menu up, Power menu entry, then B to dismiss the power menu and B again to dismiss the Steam menu).

@matte-schwartz

Copy link
Copy Markdown
Collaborator Author

Hmm, I'll take a look, I don't think I tried that specific scenario. Will poke at it and see if it's a gamescope bug or steam bug.

@matte-schwartz

Copy link
Copy Markdown
Collaborator Author
┌─────────────────────┬─────────────────────┬───────────┬──────────────────────────────────────────────────────────────┐
│       capture       │   actual X focus    │  wanted   │                           notes                            │
├─────────────────────┼─────────────────────┼───────────┼──────────────────────────────────────────────────────────────┤
│ 10:17:48 healthy    │ 0x1800006, revert=2 │ 0x1800006 │ CEF main browser view (1920x1200 grandchild of the toplevel) │
├─────────────────────┼─────────────────────┼───────────┼──────────────────────────────────────────────────────────────┤
│ 10:17:59 Steam menu │ 0x1800017, revert=2 │ 0x1800006 │ CEF's 452px sidebar browser took focus                       │
├─────────────────────┼─────────────────────┼───────────┼──────────────────────────────────────────────────────────────┤
│ 10:18:04 Power menu │ 0x1800006, revert=2 │ 0x1800006 │ modal renders in the main view, focus already back           │
├─────────────────────┼─────────────────────┼───────────┼──────────────────────────────────────────────────────────────┤
│ 10:18:12 broken     │ 0x1800006, revert=2 │ 0x1800006 │ identical to healthy                                         │

leaning towards a steam bug here, there's no difference between the broken and healthy state, and the ring appears after the first time you move the joystick or dpad.

@matte-schwartz

Copy link
Copy Markdown
Collaborator Author

@oSoMoN the bug you noticed reproduces in plain big picture mode without gamescope involved, so I'm inclined to merge this if that was the only concern

@matte-schwartz
matte-schwartz merged commit fcc1341 into ValveSoftware:master Aug 19, 2026
1 check passed
@matte-schwartz
matte-schwartz deleted the focus-ring-fixes branch August 19, 2026 17:29
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.

2 participants