mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-13 03:51:10 +00:00
353 lines
26 KiB
Markdown
353 lines
26 KiB
Markdown
# gamescope patches carried by the app
|
|
|
|
Built by `.github/workflows/build-gamescope.yml` on top of the exact gamescope the Linux runtime
|
|
ships (3.16.29, Arch Linux ARM's package, same build options), and staged from the apk over
|
|
`/usr/local/bin/gamescope` at each session start - the hosted runtime image is never touched.
|
|
The binary's shared-library needs are checked against `runtime-sonames.txt`, the runtime's own
|
|
library list, before anything is published.
|
|
|
|
- `0002-steamcompmgr-fallback-appid-focus.patch` - Armada (armada-os/armada), verbatim.
|
|
- `0009-fix-arm64-steam-night-mode.patch` - Armada, verbatim: the ARM64 client packs the
|
|
night-mode property differently; the slider did nothing.
|
|
- `0019-steamcompmgr-arm64-virtual-white.patch` - Armada, verbatim: the colour-temperature
|
|
slider's (x, y) arrives as one 64-bit element from the ARM64 client; y is recovered from x.
|
|
- `0020-color-p3-red-is-wide-gamut.patch` - Armada, verbatim.
|
|
- `0100-realtime-queue-and-gamepad-cursor.patch` - this app, two of Armada's ported by hand onto
|
|
3.16.29: realtime-priority Vulkan queues on request (`GAMESCOPE_FORCE_VULKAN_REALTIME=1`)
|
|
without CAP_SYS_NICE, which proot can never have (a no-op on KGSL Turnip, which has a single
|
|
submit-queue priority); and the gamepad-driven cursor sprite following the X pointer that XTest
|
|
moves (it sat frozen). The X pointer is asked for only while a cursor image is drawn - every
|
|
vblank while shown, every 50 ms while hidden for inactivity - since each ask is a blocking round
|
|
trip to Xwayland on the paint thread; with no image, wlserver's position is used as upstream does.
|
|
- `0101-nested-wayland-cursor-routing.patch` - this app: `gamescope_cursor_composite=2` lets the
|
|
nested Wayland session paint its cursor in the output using the real X pointer position; client
|
|
cursor surfaces are cleared to prevent a duplicate at the host pointer, while Gamescope's original
|
|
default host-cursor fallback is preserved when its no-keyboard-focus policy selects it. Empty
|
|
client cursor images and Gamescope's hidden flag still suppress the painted cursor.
|
|
- `0110-wayland-backend-touch.patch` - this app: the nested Wayland backend bound only the host's
|
|
pointer and keyboard, so a finger on the phone's screen never reached Steam. It now binds
|
|
`wl_touch` too and hands each finger to wlserver's touch path (`wlserver_touchdown` / `motion` /
|
|
`up`) - the one a Steam Deck's touchscreen drives - so what a touch does follows the client's
|
|
touch mode (Steam's Big Picture sets Passthrough: a real touch, rows scroll under a finger).
|
|
Finger ids are offset by one, since the nested pointer already moves wlserver's touch 0.
|
|
- `0111-wayland-pointer-warps-in-passthrough.patch` - this app: the nested pointer's motion goes to
|
|
wlserver as touch 0, and in Passthrough (Big Picture's touch mode) a motion for a touch that is not
|
|
down moves nothing - so in the app's touchpad mode the Steam client saw no hover and a click landed
|
|
wherever the pointer had last been. The motion now always warps the real pointer as well
|
|
(`bAlwaysWarpCursor`), which the other touch modes did already.
|
|
- `0112-restore-iconified-game-on-resume.patch` - this app: the Steam menu is an overlay that
|
|
takes input without changing the focus window, and a fullscreen wine game minimizes itself when
|
|
it loses input. gamescope only takes a window out of iconic when the focus window changes, and
|
|
wine will not activate a window it believes iconic, so after Resume the game stayed minimized: a
|
|
black screen with its small caption in the top-left corner (Titanfall 2, GE-Proton 11). The
|
|
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.
|
|
- `0115-limiter-paces-per-window.patch` - this app: on a fixed-refresh output the frame limiter
|
|
(Steam's Frame Limit) let a capped window's frames out only on every Nth refresh of a global grid,
|
|
so a frame finished just after its slot waited a whole extra slot. At 30 on 60 Hz one picture was
|
|
held for three or four refreshes - "small stops and ladders" with frame generation - where a game's
|
|
own limiter slips such a frame by one refresh. The fixed-refresh branch now paces each window by
|
|
time, as the VRR branch already did: its next frame goes out on the first refresh a capped interval
|
|
after its last one, with half a refresh of tolerance so an on-time window keeps every Nth refresh.
|
|
- `0116-hold-overlay-repaint-for-next-frame.patch` - this app: on a fixed-refresh output every
|
|
mangoapp commit asked for a repaint of its own, and mangoapp draws once per game frame just after
|
|
that frame was shown, so a 60 fps game on a 120 Hz panel was composited twice per frame - 75 frames
|
|
a second reached the screen, one in two 8 ms after the last with no new picture (Valheim, Stardew
|
|
Valley, Among Us). The VRR path already leaves such repaints to the next game frame; on a fixed
|
|
refresh output they now wait for it too, for at most `external_overlay_hold_cycles` refreshes (4,
|
|
so 30 fps at 120 Hz still merges), and are painted on their own after that.
|
|
|
|
The non-Adreno test branch's own patches are numbered 0920-0935 (0120-0135 until main took
|
|
0121-0125), so they apply after DroidDeck's. Applied in either order the tree is the same.
|
|
|
|
The system PowerVR driver uses the existing SDL/Wayland swapchain backend. Its additional patches are:
|
|
|
|
- `0920-nested-swapchain-capability-fallback.patch`: negotiate the surface's usages, mutable-format
|
|
support, image count, alpha and extent; use offscreen composition and GPU copies when required.
|
|
- `0921-powervr-single-pixel-rcas.patch`: use the existing single-pixel RCAS shader on PowerVR,
|
|
where the quad variant fails compilation.
|
|
- `0922-powervr-linear-dmabuf-import.patch`: retain the explicit linear layout when PowerVR
|
|
rejects the mutable-format query, and intersect imported-image and FD memory-type masks.
|
|
- `0923-powervr-rgba-copy-surface.patch`: require an advertised RGBA SDR copy surface matching
|
|
the shader's `rgba8` storage view. The tested workaround leaves other vendors, HDR and direct
|
|
composition unchanged; it does not restrict game-facing BGRA formats.
|
|
- `0924-sdl-preserve-explicit-refresh.patch` and `0925-sdl-wait-before-present.patch`: preserve
|
|
configured refresh across SDL events and finish composition before presenting without a
|
|
render-finished semaphore. The build runs the focused capability, format and refresh tests.
|
|
|
|
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.
|
|
- `0926-explicit-sync-switch.patch`: `GAMESCOPE_DISABLE_EXPLICIT_SYNC=1` makes the nested
|
|
(Wayland and SDL) backends report no explicit sync, so gamescope does not offer
|
|
wp_linux_drm_syncobj_manager_v1 and clients use implicit sync. The app sets it on a GPU that is
|
|
not an Adreno when the kernel is older than 6.6 (no DRM_IOCTL_SYNCOBJ_EVENTFD: an Exynos 2400e
|
|
on 6.1 logged hundreds of "timeline: DRM_IOCTL_SYNCOBJ_EVENTFD failed" and a game hung at a
|
|
black screen) or when the render node is Mali's stand-in (/dev/mali0 has no DRM syncobjs).
|
|
- `0927-shm-buffers-upload-every-commit.patch` (David Cimo, for Mali through Venus; his `0114`): with
|
|
no DRM node, Xwayland has no glamor and commits a few wl_shm buffers over and over with new pixels.
|
|
gamescope memoizes one texture per `wlr_buffer`, and a shm buffer's texture is a copy taken at
|
|
import, so the screen cycled through the first few frames forever while Steam ran on behind them.
|
|
Only dma-buf textures are memoized now; a shm buffer is uploaded on every commit. Adreno never
|
|
takes this path, since its Xwayland hands over dma-bufs. Also what XWAYLAND_NO_GLAMOR sessions need.
|
|
- `0929-log-failing-compute-pipeline.patch`: when `vkCreateComputePipelines` fails, one extra line
|
|
per shader type names the pipeline (shader type, layer count, ycbcr/colorspace masks, fp16) and the
|
|
SPIR-V capabilities its shader declares. A Mali-G615 through Venus refused all 119 composite
|
|
pipelines with VK_ERROR_INITIALIZATION_FAILED and nothing said which feature it objected to.
|
|
- `0930-composite-without-immutable-ycbcr-samplers.patch`: one composite pipeline is compiled at
|
|
start-up; when the driver refuses it, the descriptor layout is rebuilt without binding 4's immutable
|
|
YCbCr samplers (that binding then takes the slot's ordinary sampler) and the probe tried again, with
|
|
both outcomes in the log. `GAMESCOPE_NO_YCBCR_SAMPLERS=1` forces it. A Mali-G615 through Venus
|
|
refused every composite shader (blit, blur, RCAS: SPIR-V capabilities Shader, Sampled1D,
|
|
ImageQuery, no Float16) while EASU, NIS and the NV12 converter, which share the layout but never
|
|
touch binding 4, compiled.
|
|
- `0931-memory-type-fallback-keep-probe.patch`: `findMemoryType` asked for DEVICE_LOCAL|HOST_VISIBLE
|
|
memory falls back to host-visible memory without DEVICE_LOCAL, and a failure logs the requested
|
|
properties, the type bits and every memory type. The Mali bridge models a unified-memory GPU like a
|
|
discrete one (host-visible types without DEVICE_LOCAL), so the upload buffer found nothing and the
|
|
backend failed. Also: 0930's probe pipeline is kept instead of destroyed, and skipped when
|
|
`GAMESCOPE_NO_YCBCR_SAMPLERS=1` (destroying a pipeline the Venus host had refused killed the
|
|
render server).
|
|
- `0932-name-hung-up-waitable.patch`: "IWaitable hung up. Aborting." now names the waitable's class,
|
|
its fd and what the fd is (`/proc/self/fd`), and the Wayland backend's input thread says why the
|
|
compositor connection ended (protocol error or errno) first. An S24 FE aborted there after 60/120 Hz
|
|
flips and after a resume with nothing else in the log.
|
|
- `0933-flippable-image-unexportable-modifiers.patch`: a flippable (dma-buf exported) image whose
|
|
modifiers the driver supports but does not report EXPORTABLE uses them anyway, and an image with no
|
|
usable modifier fails with a log line instead of asserting in `CVulkanTexture::BInit`. The Mali
|
|
bridge forwards the phone driver's answer, which leaves EXPORTABLE out, though it exports dma-bufs.
|
|
- `0934-flippable-image-no-backend-modifiers.patch`: when the nested backend offers no modifier at all
|
|
for a flippable image's format, `CVulkanTexture::BInit` tries linear and logs it instead of asserting
|
|
`!modifiers.empty()`. NieR Replicant ended the session there on a Pixel 9a under Venus.
|
|
- `0935-flippable-image-linear-when-driver-rejects-all.patch`: when the driver answers every offered
|
|
modifier with `VK_ERROR_FORMAT_NOT_SUPPORTED` and linear was among them, the flippable image tries
|
|
linear anyway; if it cannot be made, `BInit` fails as before. An r32p1 Mali-G610 (Vulkan 1.1) behind
|
|
the Mali bridge rejected both of the backend's modifiers for AR24, so the session ended at start
|
|
(0933's "no usable modifier"); the bridge makes such images without the handle type and exports
|
|
them through a dma-heap.
|
|
|
|
The shared DroidDeck patch also includes `0116-release-cursor-shm-buffers.patch`: pointer cursor
|
|
surfaces have no X11/xdg window association, so routing their commits through `g_PendingCommits`
|
|
retained every old SHM buffer until the surface was destroyed. It ignores cursor-role commits before
|
|
taking the buffer lock; X11 cursor images still follow the existing MouseCursor path. Window commits
|
|
awaiting association are unchanged.
|
|
|
|
The live regression probe is `tools/tests/test_gamescope_cursor_shm.py`. Run it
|
|
inside an active Gamescope session with access to that process's `/proc` fd
|
|
directory. It submits 20 distinct SHM cursor buffers and requires at most the
|
|
current buffer to remain open, then none after surface destruction. The
|
|
`--unassigned-control` variant omits the cursor role and verifies that all 20
|
|
commits awaiting a window association remain queued until surface destruction.
|
|
|
|
Device validation (2026-10-20): the stock component retained 20/20 cursor buffers;
|
|
the patched component retained 1/20, then 0 after destruction. The unassigned
|
|
control retained 20/20 and then 0. In Backpack Battles, two batches of ten touch
|
|
drags leaked 188 and 197 SHM descriptors before the fix; the same batches added
|
|
zero after the fix (112 total descriptors before and after each batch).
|
|
|
|
## 0117 WSI: cache the present-time Xwayland bypass check
|
|
|
|
`QueuePresentKHR` in the WSI layer calls `canBypassXWayland` for every swapchain on
|
|
every present. Each call makes several synchronous X requests (window geometry,
|
|
`_WINE_ALLOW_FLIP`, parent window, a child-window scan, the toplevel window). At
|
|
high frame rates that is thousands of round trips a second: on an AYANEO Pocket FIT
|
|
running AIO Graphics Test uncapped, Xwayland used 24-32% of a core and the game's
|
|
present thread sat about half idle.
|
|
|
|
The patch can keep the result for `DROIDDECK_WSI_BYPASS_CHECK_MS` milliseconds. Unset
|
|
or 0 keeps the per-present check; the app sets 100. With 100, a window resize or a flip/obscure change is seen up to 100 ms
|
|
later than before; surface creation and capability queries still check every time.
|
|
|
|
## 0118 Wayland: preserve signed plane positions
|
|
|
|
Fill scaling and Steam magnification can place the image at negative x/y coordinates.
|
|
The Wayland backend converted these signed positions through an unsigned size helper:
|
|
at host scale 120, -120 became 35791275 and the image moved off-screen while overlays
|
|
could remain visible. A separate signed conversion uses a 64-bit intermediate and
|
|
retains the existing ceiling convention; viewport sizes keep their original helper.
|
|
|
|
The component build runs `wayland_scaling` for negative crop offsets, positive/zero
|
|
positions, fractional host scales and wide intermediates. This patch may be worth
|
|
upstreaming: the same unsigned position calls were present in ValveSoftware/gamescope
|
|
3.16.29 and master `d175688ad4ec490dc7b0695c5c62f8807684a1f1` when checked on
|
|
2026-10-26. Carry it here until an upstream release containing the fix is adopted.
|
|
|
|
Device validation on AYN Thor (1280x720 session, 1920x1080 Android output):
|
|
800x600 and 1600x600 source windows retained their image through Auto, Fit,
|
|
Stretch, Fill, Integer and 2x Steam magnification, including returning to Fit.
|
|
The old component lost the image in Fill and magnification. The tested executable
|
|
matches the pinned p14 archive and the locally assembled APK. Hardware checks used
|
|
native Gamescope properties; the physical Back+LB shortcut was not independently
|
|
confirmed. The original APK and runtime were restored after validation.
|
|
|
|
## 0119 WSI: default image count while the frame limiter runs
|
|
|
|
The app sets `GAMESCOPE_WSI_MIN_IMAGE_COUNT=8` so an uncapped game keeps drawing while
|
|
gamescope turns its buffers around. With Steam's Frame Limit on, gamescope forces FIFO,
|
|
and the layer always hands the driver MAILBOX, so a game with eight images could queue
|
|
up to seven frames inside gamescope before it blocked: each one adds a frame of input
|
|
latency at the limited rate.
|
|
|
|
While the limiter forces FIFO, the layer now reports and enforces its default minimum
|
|
of 3 images instead of the raised count; with the limiter off it uses the raised count
|
|
as before. DXVK and VKD3D-Proton already recreate their swapchain when the limiter
|
|
changes (the layer returns `VK_ERROR_OUT_OF_DATE_KHR`), so their image count follows
|
|
the Quick Access menu. Other apps keep their swapchain on a limiter change; they now
|
|
get `VK_SUBOPTIMAL_KHR` while their image count no longer matches, unless the layer
|
|
marks them as not taking it (`NoSuboptimal`).
|
|
|
|
## 0120 Wayland: nested refresh follows the host
|
|
|
|
`-r` fixes the refresh gamescope paces at, and the frame limiter and Steam's Frame Limit
|
|
slider use the same value. The app passes the panel refresh at session start, so a
|
|
panel switched to another mode during the session (for example in the device's own
|
|
quick settings) left gamescope pacing at the old rate until the next session; only the
|
|
reported output refresh changed.
|
|
|
|
With `GAMESCOPE_NESTED_REFRESH_FOLLOWS_HOST=1`, a refresh the host compositor reports
|
|
through presentation feedback replaces the `-r` value, so pacing and the limiter switch
|
|
with the panel. `-r` still sets the starting rate, which avoids gamescope assuming
|
|
60 Hz until the first frame is shown. When the refresh changes, gamescope also sends
|
|
its `gamescope_control` display information to Steam again; Steam otherwise reads it
|
|
only when it binds.
|
|
|
|
A host mode switch can report a passing rate for a few frames (72 Hz between 144 and
|
|
120 Hz on the AYANEO Pocket FIT), so with following on a new rate is taken once it has
|
|
been reported for 300 ms. Without the variable the reported rate is taken at once, as
|
|
upstream does.
|
|
|
|
The rate comes from the feedback of a frame gamescope presents, so with nothing moving
|
|
on screen no feedback arrived and the old rate stayed. With following on, gamescope
|
|
presents once when the host's `wl_output` mode changes (DroidDeck's compositor sends
|
|
the new mode) and keeps presenting while a new rate is pending (since p16).
|
|
|
|
## 0121 Xwayland: skip the X tree walk for a repeated window override
|
|
|
|
The WSI layer sends `override_window_content` on every present of an X11 swapchain.
|
|
gamescope first walked from that window to its top-level window with `XQueryTree`, one
|
|
blocking X round trip per level under the wlserver lock, and only then noticed that the
|
|
same swapchain already overrides that window. At thousands of frames per second that is
|
|
thousands of round trips to Xwayland every second.
|
|
|
|
The override now remembers the window it was asked for. When the same swapchain and
|
|
surface ask for the same window again within 100 ms, the request returns at once; after
|
|
100 ms the walk runs again, so a window that moved to another top level is still found.
|
|
|
|
## 0122 Wayland: wake steamcompmgr after reading host events
|
|
|
|
Backport of upstream commit 2962992 by Matthew Schwartz (ValveSoftware/gamescope).
|
|
The nested Wayland backend's input thread reads the host display, but steamcompmgr
|
|
does not watch that file descriptor, so buffer releases and presentation feedback
|
|
queued by that read could wait up to one refresh for steamcompmgr's next poll. The
|
|
input thread now wakes steamcompmgr after each read.
|
|
|
|
## 0123 commit: fence lifetime fixes from upstream
|
|
|
|
Backport of upstream commits 0efe47c and 666c52b by Matthew Schwartz (ValveSoftware/gamescope).
|
|
Pending commits share ownership of their completion list with the Xwayland context, so a late
|
|
fence signal can no longer append to a list freed with the context; and a commit being destroyed
|
|
no longer hands the image waiter a new reference to itself. Both touch the code 0124 changes.
|
|
|
|
## 0124 commit: mark a finished frame done at import
|
|
|
|
Every game frame took two passes of steamcompmgr's loop: the import duplicated the buffer's
|
|
fence and handed the commit to the image waiter (epoll add), then the waiter thread saw the fence
|
|
signalled, removed and closed it and woke steamcompmgr again to handle the finished commit.
|
|
`update_wayland_res` now polls the fence without waiting first. A frame that is already finished
|
|
is marked done at once - no duplicate fence, no epoll add or delete, no waiter wake - and the main
|
|
loop handles it in the same pass, after the import (FIFO commits still wait for a vblank). The
|
|
`commit_ready_check` convar (default on, `gamescopectl commit_ready_check 0`) turns it off; the
|
|
`xwm` log counts fences ready at import and waited for, every 10 s.
|
|
|
|
## 0125 steamcompmgr: share mangoapp's frame times with DroidDeck's HUD
|
|
|
|
DroidDeck's in-app HUD shows the same frame rate and frame times as Steam's performance overlay.
|
|
DroidDeck's mangoapp (Valve's 20260902.1 build) reads the field at byte 16 of gamescope's messages as
|
|
its frame time; gamescope 3.16.29 puts `app_frametime_ns` there, the time between signalled commits of
|
|
the connector's windows (`mangoapp_nudge_app_frame`). With `DROIDDECK_HUD_FRAMES` set (droiddeck-session
|
|
sets `/run/droiddeck-hud/frames`), gamescope maps that file and writes the same commits of connector 0,
|
|
whether or not a mangoapp runs, plus the focused pid, output size, refresh, Steam focus and engine name.
|
|
The layout is in the comment above `droiddeck_hud_frames_map`; the app reads it in `hudframes.c`.
|
|
|
|
## 0126 steamcompmgr: a topmost focus window keeps other processes' windows beneath it
|
|
|
|
EA games on Steam start through the EA app. Its Steam launcher (`EASteamLauncher.exe`) shows the
|
|
game's splash as a layered tool window, and the EA app (`EADesktop.exe`, another process) opens its
|
|
sign-in window `WS_EX_TOPMOST`. gamescope took the splash as the sign-in's override window, so it was
|
|
painted over most of the sign-in and became gamescope's keyboard focus window (Burnout Paradise
|
|
Remastered, `gamescopectl focus_info`). On Windows a topmost window stays above every window that is neither topmost nor
|
|
owned by it, so a window from another process that is neither is no longer an override candidate for
|
|
a topmost focus window. Both windows' pids must be known; gamescope keeps -1 when XRes cannot name the
|
|
client, which says nothing about the process. Same-process popups, topmost popups (WebView2 and CEF menus) and windows
|
|
transient for the focus window are unchanged. gamescope read `_WINE_HWND_EXSTYLE` only from a
|
|
PropertyNotify, and it listens for those from map on, so the style Wine sets before mapping a window
|
|
was never known; `map_win` now reads it with the other properties, which the layered-launcher and
|
|
Xalia rules rely on too. `tools/tests/test_gamescope_override_stacking.py` compiles the rule from
|
|
this patch.
|
|
|
|
## 0127 steamcompmgr: a three way relative mouse switch
|
|
|
|
`relative_mouse` is read where the decision is made: 0 leaves it to the rule below, 1 forces relative
|
|
motion for the focused game except Steam's own windows and composites the cursor, 2 never goes relative.
|
|
For games that confine a visible cursor and warp it, which the automatic rule cannot see.
|
|
|
|
## 0128 steamcompmgr: a hidden cursor under any pointer constraint is relative
|
|
|
|
The rule only counted a lock with a cursor position hint. A game that hides the cursor and confines the
|
|
pointer through a grab (Wine's ClipCursor) has a constraint with no hint, so the nested pointer stayed
|
|
absolute, the constraint held the X pointer still and no raw motion was produced: the camera froze.
|
|
0133 narrows this, and the upstream hint rule, to games that hold the X pointer.
|
|
|
|
## 0129 steamcompmgr, wayland: release a pointer lock at the game's pointer
|
|
|
|
A game can move the X pointer while it holds the lock (Wine's ClipCursor recentres it), and the nested host
|
|
never hears about it. On unlock the host's next absolute motion dropped the pointer back at its old position,
|
|
often near a corner. The unlock now sends the X pointer as a cursor position hint and commits it first. A cursor
|
|
forced On by `relative_mouse` also stays visible while idle, since the host cursor is hidden during the lock.
|
|
|
|
## 0130 steamcompmgr: fit the X screen to a larger fullscreen window
|
|
|
|
A game can open a fullscreen window larger than its X screen (Valheim at 1366x768 on 1280x720). The X server
|
|
keeps the pointer inside the screen, so the outer part of the window could not be reached and motion past the
|
|
edge had to be undone. The server's mode now follows such a window, and returns once the focused window fits,
|
|
focus moves to Steam or no window has focus.
|
|
|
|
## 0131 steamcompmgr, wlserver: follow a game's pointer warps
|
|
|
|
Under a nested host the seat's absolute position came from the host, so a game that warped a visible X pointer
|
|
to the screen centre read the distance back to the host's position on the next motion, and the view spun. When
|
|
the X pointer stays far from the seat position while input moves, gamescope takes the host's lock and applies
|
|
each frame's motion to the X pointer itself until the game stops warping. Absolute motion also sends relative
|
|
motion, as a real compositor does, so XInput2 raw motion reaches games that read only that. A touch moves the
|
|
pointer without relative motion, so tapping the screen does not turn the camera.
|
|
|
|
The X pointer is read in root coordinates and the seat position is local to the focused window, so the X pointer
|
|
is moved into the focused window's coordinates before the two are compared. Without that, any window not at the
|
|
root origin (a dialog, or a windowed program) looked like a game warping the pointer, following started, and the
|
|
pointer was pinned to the window's corner. A touch always lands where the finger is and ends following, so taps
|
|
work in every window.
|
|
|
|
## 0132 sdl, steamcompmgr: follow a game's window mode
|
|
With `--follow-game-window-mode`, the outer window of a nested SDL gamescope follows the focused game window. A window
|
|
asking for `_NET_WM_STATE_FULLSCREEN`, or a Steam game covering the X screen without minimise or maximise functions or
|
|
decorations in `_MOTIF_WM_HINTS`, makes the outer window fullscreen on the desktop. Anything else makes it a normal window of the
|
|
game window's size, or a maximised one when that size does not fit the desktop with its title bar and panel. A change is applied only when the wanted mode changes, so a manual resize or Super+F stays.
|
|
|
|
## 0133 steamcompmgr, wlserver: a pointer constraint is relative only while the game holds the X pointer
|
|
|
|
A game that hides the system cursor, draws its own and grabs the pointer to its window (Stardew Valley, Balatro) gets a lock
|
|
from Xwayland, which emulates the game's warps with a cursor position hint. Upstream kept any hinted lock relative and 0128
|
|
added every other constraint, so gamescope held the host's pointer lock and DroidDeck turned every direct touch swipe into
|
|
camera motion: dragging in those games only moved their cursor. Without relative mode such a lock still follows gamescope's
|
|
relative motion and touches still place the pointer. gamescope now compares the motion it sends (seat movement, or relative
|
|
motion while the seat is locked) with how the X pointer moves. A hinted constraint starts relative, as upstream, also when its hint arrives with the game's first warp, and is released
|
|
after 30 moving frames on which the X pointer follows. Any constraint goes relative once the X pointer moves less than a quarter
|
|
of that motion on three frames within half a second, as it does when the game recentres it or clips it to a small rectangle
|
|
(PEAK). `tools/tests/test_gamescope_pointer_hold.py` compiles the rule from this patch.
|