mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-12 11:31:08 +00:00
fix: backport upstream gamescope fix for games left behind Steam's loading screen (#401)
This commit is contained in:
1 parent
4454a6d39a
commit
9310d19857
4 files changed
+132
-2
No files matched your search
@@ -0,0 +1,89 @@
|
||||
# Steam session focus: how it works and how to debug it
|
||||
|
||||
Written 2026-10-07 after the "game opens behind Steam" reports (Skyrim SE, A Plague Tale:
|
||||
Innocence, Burnout Paradise Remastered), fixed by gamescope patch 0114 (#401). It records what a
|
||||
game needs to get focus in a Steam session and how to inspect a live session on a device, for the
|
||||
next report that looks like this.
|
||||
|
||||
## How a game gets focus
|
||||
|
||||
The Steam session runs two Xwayland servers, as SteamOS does: the client on `:0`, games on `:1`
|
||||
(`--xwayland-count 2`, `STEAM_MULTIPLE_XWAYLANDS=1` in `droiddeck-session`). Focus is decided in
|
||||
three steps, and a window can drop out at each one:
|
||||
|
||||
1. **gamescope lists candidates.** For each server it collects windows that are mapped, of class
|
||||
InputOutput, not fully transparent (`_NET_WM_WINDOW_OPACITY` above 0), not an overlay, and
|
||||
carry an app id (`STEAM_GAME`). It publishes them on `:0`'s root as `GAMESCOPE_FOCUSABLE_APPS`
|
||||
and `GAMESCOPE_FOCUSABLE_WINDOWS` (window, app id, pid triples). It leaves out of that published
|
||||
list any window it believes is override-redirect, 1x1, or skip-taskbar-and-pager without
|
||||
fullscreen.
|
||||
2. **Steam picks an order.** The client reads those lists and writes
|
||||
`GAMESCOPECTRL_BASELAYER_APPID`, for example `413091, 489830, 769`. When the running game has
|
||||
no window in the focusable list, Steam puts itself (769) ahead of it.
|
||||
3. **gamescope follows Steam.** The virtual connector strategy in a Steam session is
|
||||
SteamControlled: the first app id in the base layer with a candidate window wins.
|
||||
|
||||
gamescope works from its own cached copy of each window's attributes, not from X. A window that X
|
||||
shows mapped and tagged can still be missing in step 1 if that cache is stale.
|
||||
|
||||
## What the bug looked like
|
||||
|
||||
- On screen: Steam's black loading screen with only **ABORT GAME**, the game audible underneath.
|
||||
Resume from the Steam menu does not help.
|
||||
- `GAMESCOPE_FOCUSED_APP` stays `769`; `GAMESCOPECTRL_BASELAYER_APPID` has `769` ahead of the
|
||||
game's app id; the game's window is missing from `GAMESCOPE_FOCUSABLE_WINDOWS`.
|
||||
- On `:1` the game window is mapped, `WM_STATE` Normal, `STEAM_GAME` set, with no opacity,
|
||||
`_NET_WM_STATE` or overlay properties.
|
||||
- gamescope's `focus_info` shows server 1's own focus on the game while the global focus is Steam.
|
||||
|
||||
Cause: wine creates the game's window override-redirect and makes it managed before mapping it,
|
||||
which sends no X event. gamescope read the attributes when the window was created and refreshed
|
||||
`override_redirect` only from ConfigureNotify, so when its read came before wine's change it kept
|
||||
the window as override-redirect and left it out of the list Steam reads. It is a race: Skyrim
|
||||
failed about once in six launches on the Thor, on the first launch after a session start. Upstream
|
||||
fixed it by taking the flag from MapNotify (ValveSoftware/gamescope 3829340, our patch 0114).
|
||||
|
||||
The launcher and the game both had window id `0x1c00003` on `:1`: with few clients on that server
|
||||
the X server hands the game the launcher's freed id. That was not the cause (the game window came
|
||||
7 s after the launcher's was destroyed), but it makes logs easy to misread.
|
||||
|
||||
## Inspecting a live session from adb
|
||||
|
||||
Everything below runs on the device as the app's uid (`su 10195` on the Thor, `u0_a195`), using
|
||||
the rootfs's own binaries through its loader, so nothing has to enter proot:
|
||||
|
||||
```sh
|
||||
R=/data/user/0/com.droiddeck.launcher/files/linuxfs
|
||||
$R/usr/lib/ld-linux-aarch64.so.1 --library-path $R/usr/lib $R/usr/bin/xprop -display :0 -root \
|
||||
GAMESCOPE_FOCUSED_APP GAMESCOPE_FOCUSABLE_WINDOWS GAMESCOPECTRL_BASELAYER_APPID
|
||||
```
|
||||
|
||||
- The X servers answer on their **abstract** sockets (`\0/tmp/.X11-unix/X0`, `X1`); xprop finds
|
||||
them by itself. The socket files under `linuxfs/tmp/.X11-unix` can be stale. Nothing answers
|
||||
while the session is in the background or the screen is off (the session is stopped).
|
||||
- The `VAR=… cmd` prefix does not survive `timeout` or `nohup`; put the command in a small
|
||||
`/system/bin/sh` script in `/data/local/tmp` and run that.
|
||||
- gamescope's focus state, written to the session's `session.log` (in
|
||||
`Download/DroidDeck/<date>-NN-steam/`):
|
||||
|
||||
```sh
|
||||
export XDG_RUNTIME_DIR=/data/user/0/com.droiddeck.launcher/files/.wayland-rt WAYLAND_DISPLAY=gamescope-0
|
||||
G="$R/usr/lib/ld-linux-aarch64.so.1 --library-path $R/usr/lib $R/usr/bin/gamescopectl"
|
||||
$G log_focus debug; $G focus_info
|
||||
```
|
||||
|
||||
It prints the global focus, input, keyboard and override windows, the connector strategy, and
|
||||
each server's own focus and keyboard focus. Disagreement between a server's focus and the
|
||||
global one points at step 1 or 2 above.
|
||||
- To tell a stale gamescope cache from a real X problem, make gamescope re-read the window: move it
|
||||
by 1 px and back (ConfigureNotify), or unmap and map it (MapNotify). If the game comes forward,
|
||||
gamescope's cache was wrong. A restack of a window already on top sends no event and proves
|
||||
nothing.
|
||||
- To test gamescope with a candidate binary without rebuilding the apk, put it in the rootfs (for
|
||||
example `/opt/gstest/gamescope`, owned by the app's uid) and add
|
||||
`PATH=/opt/gstest:/usr/local/bin:/usr/bin:/bin` to `Download/droiddeck-env`. The session log's
|
||||
first lines name the gamescope that ran. Remove the line afterwards: the app stages its own
|
||||
gamescope over `/usr/local/bin` at every session start.
|
||||
- Never `pkill -f <pattern>` inside `su -c '…'`: the pattern matches that shell's own command line.
|
||||
Killing `SkyrimSE.exe` that way took the whole session down. Kill by pid.
|
||||
- Skyrim's menus take adb key events (`input keyevent 20` down, `66` enter); taps do nothing.
|
||||
@@ -38,6 +38,12 @@ library list, before anything is published.
|
||||
iconify request is remembered and the window goes back to NormalState before input returns to it,
|
||||
then focus is handed over again. `GAMESCOPE_RESTORE_FOCUS_WINDOW` on the root window asks for the
|
||||
same restore from outside (the session script's resume watcher).
|
||||
- `0114-take-override-redirect-from-mapnotify.patch` - upstream (ValveSoftware/gamescope 3829340),
|
||||
verbatim; drop it once the runtime's gamescope includes it. Wine creates a game's window
|
||||
override-redirect and makes it managed before mapping it, which sends no X event. When gamescope
|
||||
read the window's attributes first, it kept the window as override-redirect and left it out of
|
||||
`GAMESCOPE_FOCUSABLE_WINDOWS`, so the client kept its loading screen over the running game
|
||||
(Skyrim SE, A Plague Tale: Innocence, on the games' own Xwayland). MapNotify now refreshes the flag.
|
||||
|
||||
Sixteen more of Armada's patches are DRM/lease/HDR-on-KMS work for a native display, which this
|
||||
app's Wayland-hosted gamescope never reaches, or need a newer gamescope than the runtime has.
|
||||
@@ -0,0 +1,35 @@
|
||||
From: Matthew Schwartz <matthew.schwartz@linux.dev>
|
||||
Subject: [PATCH] steamcompmgr: take override-redirect from MapNotify
|
||||
|
||||
Wine creates a toplevel as override-redirect and flips it to managed with
|
||||
XChangeWindowAttributes while it is unmapped, which generates no X event.
|
||||
When add_win reads the attributes before the flip, the window stays cached
|
||||
as override-redirect and is left out of GAMESCOPE_FOCUSABLE_APPS. Steam
|
||||
then keeps its loading screen over the game, as with A Plague Tale:
|
||||
Innocence (752590). Before 2238420d, the focus nudge's move usually
|
||||
refreshed the flag.
|
||||
|
||||
MapNotify carries the current override-redirect state, so refresh the
|
||||
cached flag there rather than re-reading the attributes.
|
||||
|
||||
Closes: #2457
|
||||
---
|
||||
src/steamcompmgr.cpp | 4 ++++
|
||||
1 file changed, 4 insertions(+)
|
||||
|
||||
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
|
||||
index f00b62c..cb3b9c6 100644
|
||||
--- a/src/steamcompmgr.cpp
|
||||
+++ b/src/steamcompmgr.cpp
|
||||
@@ -8778,7 +8778,11 @@ void xwayland_ctx_t::Dispatch()
|
||||
steamcompmgr_win_t * w = find_win(ctx, ev.xmap.window);
|
||||
|
||||
if (w && w->xwayland().id == ev.xmap.window)
|
||||
+ {
|
||||
+ // Wine flips override-redirect while unmapped and the flip itself sends no event.
|
||||
+ w->xwayland().a.override_redirect = ev.xmap.override_redirect;
|
||||
map_win(ctx, ev.xmap.window, ev.xmap.serial);
|
||||
+ }
|
||||
break;
|
||||
}
|
||||
case UnmapNotify:
|
||||
@@ -1,3 +1,3 @@
|
||||
GAMESCOPE_REPO=Droid-Deck/DroidDeck-Components
|
||||
GAMESCOPE_TAG=gamescope-3.16.29-p8
|
||||
GAMESCOPE_SHA256=65fcc17af1aee4944a42ce1e688db61ceb10db5f299f0de75e338f56646e2953
|
||||
GAMESCOPE_TAG=gamescope-3.16.29-p9
|
||||
GAMESCOPE_SHA256=7123a37bdbcd0d76d4bd1d7490e5d2017221e036395fdd48c1e92458594ca769
|
||||
Reference in new issue
Block a user