[PR #309] [MERGED] feat: patched Xwayland + gamescope pinning for arbitrary resolutions #293

Closed
opened 2026-10-15 12:50:37 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/games-on-whales/gow/pull/309
Author: @JBailes
Created: 4/14/2026
Status: ✅ Merged
Merged: 4/18/2026
Merged by: @ABeltramo

Base: master ← Head: feat/patched-xwayland-arbitrary-resolutions


📝 Commits (8)

  • dc8c48d feat: patched Xwayland + gamescope pinning for arbitrary resolutions
  • ca7ea6e debug: verbose xwayland build script for CI diagnosis
  • 7dcd57f fix: add libxshmfence-dev and libbsd-dev for xwayland build
  • 4864975 fix: add mesa-common-dev for dri.pc during xwayland build
  • 9f0a2d0 prismlauncher: pin community PPA codename to noble
  • ad24ec5 prismlauncher: install from official GitHub portable release
  • c778b0e prismlauncher: fix jq filter — use contains/endswith not test()
  • 4d43898 prismlauncher: fix jq word-splitting — chain select() with pipes

📊 Changes

4 files changed (+135 additions, -13 deletions)

View changed files

📝 apps/prismlauncher/build/Dockerfile (+22 -11)
📝 apps/steam/build/scripts/startup.sh (+4 -1)
📝 images/base-app/build/Dockerfile (+105 -0)
📝 images/base-app/build/scripts/launch-comp.sh (+4 -1)

📄 Description

Problem

Games streamed through gow can't render at non-standard native resolutions. A 3:2 laptop panel (e.g. 2256x1504, Framework 13 / Surface / MBP-style) or ultrawide/super-ultrawide gets letterboxed: game renders at the nearest standard 16:9 preset gamescope knows about (e.g. 2560x1440), gamescope scales that into the non-16:9 output canvas, black bars appear.

Root cause isn't in gow or Wolf — it's Xwayland's hardcoded xwl_output_fake_modes[] table in hw/xwayland/xwayland-output.c. Xwayland unconditionally synthesizes a list of "standard" modes (1920x1080, 2560x1440, 3840x2160, ...) and advertises them to X11 clients via XRandR emulation, regardless of what the Wayland compositor actually exposes. There is no upstream flag, env var, or Wayland protocol that disables this — confirmed by reading the xserver source.

Fix

Two changes, neither large:

1. Patched Xwayland, built into images/base-app

New xwayland-builder stage in images/base-app/build/Dockerfile:

  • Resolves the latest xwayland-* stable tag from gitlab.freedesktop.org/xorg/xserver at build time.
  • Downloads the tarball, applies a one-line sed transformation, builds with meson (Xwayland only, skipping Xorg/Xvfb/Xdmcp).
  • Final stage COPYs the built tree into /usr/local, and a symlink at /usr/bin/Xwayland shadows the distro binary for anything that hardcodes the path.

The patch (targets the symbol, not line numbers):

sed -i 's/ARRAY_SIZE(xwl_output_fake_modes)/0/g' hw/xwayland/xwayland-output.c

Every for (i = 0; i < ARRAY_SIZE(xwl_output_fake_modes); i++) becomes a zero-trip loop, the compiler drops it, the fake-mode list vanishes. Result: Xwayland advertises exactly what the Wayland compositor says — nothing more.

Build-time guardrails:

  • BEFORE=$(grep -c ...) — must find ≥1 match before the sed, otherwise build aborts with a message pointing to "upstream structure may have changed, refresh the patch".
  • AFTER=$(grep -c ...) — must be 0 after the sed, else build aborts.
  • Xwayland -version is run in the final stage to confirm the binary is present and PATH resolution picks ours.

Why the latest-stable tag at build time and not a pinned version: Valve's SteamOS xorg-xwayland-24.1.9-1.1 is a stock upstream rebuild with zero Valve patches, so "match Valve" collapses to "match upstream stable". Pinning adds a maintenance burden (manual bumps when CVEs ship) without a corresponding benefit. Pulling at build time means each gow image rebuild picks up current Xwayland.

Why sed on a sentinel symbol, not a line-numbered diff: line numbers in xwayland-output.c shift when upstream adds or moves code. A symbol-based transformation survives refactors as long as the array keeps its name. xwl_output_fake_modes has existed under that name since the feature landed in 2019.

2. Pin gamescope's internal resolution

apps/steam/build/scripts/startup.sh and images/base-app/build/scripts/launch-comp.sh now pass -w "${GAMESCOPE_WIDTH}" -h "${GAMESCOPE_HEIGHT}" alongside the existing -W/-H. Orthogonal to the Xwayland patch: pins gamescope's nested Wayland output size so there's no room for a downstream component to round to a "standard" size. Matters for the future split-screen work where gamescope instances run at quarter-size (1128x752, etc.).

What this unblocks

Short term: users with 3:2 laptops, ultrawide monitors, or any non-16:9 native resolution get true full-screen rendering at their panel's actual size instead of a letterboxed approximation.

Medium term: prerequisite for party-mode / split-screen streaming. In that architecture each player gets a per-tile gamescope instance at an arbitrary resolution (e.g. 2x2 of half-by-half dimensions). Without the Xwayland fix, every tile would round to the nearest standard mode and be letterboxed — making split-screen visually broken.

Risks / caveats

  • Legacy X11 apps that call XRRSetScreenConfig() to mode-switch now have only one mode available and will no-op or error. In practice: gamescope handles this by rendering the app into its own window and scaling, which is the same outcome as before. No real regression.
  • Xwayland desktop sessions (XFCE et al.) see a single mode. Fine — they're window managers, not mode-switchers.
  • Build-time dependency on git ls-remote to freedesktop.org. If that site is unreachable at build time, CI fails. Acceptable failure mode; mirror can be added later if it becomes a problem.
  • Upstream sentinel removal. If Xwayland ever removes xwl_output_fake_modes (e.g. because they finally made the feature configurable), our sed finds nothing and the build fails with a clear message to refresh. This is the correct failure mode — we'd want to know and choose a new approach rather than silently building an unpatched binary.
  • Image bake time. Adds ~3-5 min to the base-app build (Xwayland compile). All descendants (Steam, Lutris, Heroic, ES-DE, XFCE, Firefox, Kodi, Pegasus, Prismlauncher, Retroarch) inherit the patched binary at no extra cost.

Testing

Not locally buildable in this environment (Proxmox LXC-docker shim doesn't support docker build). CI is the first real build — if meson fails or the sentinel assertions trip, it'll be visible in the workflow log.

Once merged and an image is published, verification:

  • Xwayland -version inside any base-app-derived container runs and reports a version
  • On a 3:2 or ultrawide client, launch Steam with RUN_GAMESCOPE=true and GAMESCOPE_MODE="-b -w $W -h $H" — game's video settings should list only the native resolution, no standard-mode fallbacks
  • Moonlight stream overlay shows the native resolution end-to-end, no letterbox
  • Existing 16:9 users see no regression (native mode is 1920x1080 / 2560x1440 / etc. which the compositor was already advertising)

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/games-on-whales/gow/pull/309 **Author:** [@JBailes](https://github.com/JBailes) **Created:** 4/14/2026 **Status:** ✅ Merged **Merged:** 4/18/2026 **Merged by:** [@ABeltramo](https://github.com/ABeltramo) **Base:** `master` ← **Head:** `feat/patched-xwayland-arbitrary-resolutions` --- ### 📝 Commits (8) - [`dc8c48d`](https://github.com/games-on-whales/gow/commit/dc8c48d1c2f1bc6d878194358847f9e4cda91cdd) feat: patched Xwayland + gamescope pinning for arbitrary resolutions - [`ca7ea6e`](https://github.com/games-on-whales/gow/commit/ca7ea6ea6fe747d941661f13b9d0afaca12c62f0) debug: verbose xwayland build script for CI diagnosis - [`7dcd57f`](https://github.com/games-on-whales/gow/commit/7dcd57f48ec1b8f811d1059e75ea1702e9613fe0) fix: add libxshmfence-dev and libbsd-dev for xwayland build - [`4864975`](https://github.com/games-on-whales/gow/commit/4864975289c16b16c19ae27ffa392cb3f83a6efe) fix: add mesa-common-dev for dri.pc during xwayland build - [`9f0a2d0`](https://github.com/games-on-whales/gow/commit/9f0a2d0c4eb77c5b6220b766a4bd54e36a64cb9f) prismlauncher: pin community PPA codename to noble - [`ad24ec5`](https://github.com/games-on-whales/gow/commit/ad24ec5569f031857581239d8cf97420b63ab399) prismlauncher: install from official GitHub portable release - [`c778b0e`](https://github.com/games-on-whales/gow/commit/c778b0e40b449da6253e61168d1c831f29ad1226) prismlauncher: fix jq filter — use contains/endswith not test() - [`4d43898`](https://github.com/games-on-whales/gow/commit/4d43898380786055c0caa7c9736b9e0cfe97e076) prismlauncher: fix jq word-splitting — chain select() with pipes ### 📊 Changes **4 files changed** (+135 additions, -13 deletions) <details> <summary>View changed files</summary> 📝 `apps/prismlauncher/build/Dockerfile` (+22 -11) 📝 `apps/steam/build/scripts/startup.sh` (+4 -1) 📝 `images/base-app/build/Dockerfile` (+105 -0) 📝 `images/base-app/build/scripts/launch-comp.sh` (+4 -1) </details> ### 📄 Description ## Problem Games streamed through gow can't render at non-standard native resolutions. A 3:2 laptop panel (e.g. 2256x1504, Framework 13 / Surface / MBP-style) or ultrawide/super-ultrawide gets letterboxed: game renders at the nearest standard 16:9 preset gamescope knows about (e.g. 2560x1440), gamescope scales that into the non-16:9 output canvas, black bars appear. Root cause isn't in gow or Wolf — it's **Xwayland's hardcoded `xwl_output_fake_modes[]`** table in `hw/xwayland/xwayland-output.c`. Xwayland unconditionally synthesizes a list of "standard" modes (1920x1080, 2560x1440, 3840x2160, ...) and advertises them to X11 clients via XRandR emulation, regardless of what the Wayland compositor actually exposes. There is no upstream flag, env var, or Wayland protocol that disables this — confirmed by reading the xserver source. ## Fix Two changes, neither large: ### 1. Patched Xwayland, built into `images/base-app` New `xwayland-builder` stage in `images/base-app/build/Dockerfile`: - Resolves the **latest `xwayland-*` stable tag** from `gitlab.freedesktop.org/xorg/xserver` at build time. - Downloads the tarball, applies a one-line sed transformation, builds with meson (Xwayland only, skipping Xorg/Xvfb/Xdmcp). - Final stage `COPY`s the built tree into `/usr/local`, and a symlink at `/usr/bin/Xwayland` shadows the distro binary for anything that hardcodes the path. **The patch** (targets the symbol, not line numbers): ```sh sed -i 's/ARRAY_SIZE(xwl_output_fake_modes)/0/g' hw/xwayland/xwayland-output.c ``` Every `for (i = 0; i < ARRAY_SIZE(xwl_output_fake_modes); i++)` becomes a zero-trip loop, the compiler drops it, the fake-mode list vanishes. Result: Xwayland advertises exactly what the Wayland compositor says — nothing more. Build-time guardrails: - `BEFORE=$(grep -c ...)` — must find ≥1 match before the sed, otherwise build aborts with a message pointing to "upstream structure may have changed, refresh the patch". - `AFTER=$(grep -c ...)` — must be 0 after the sed, else build aborts. - `Xwayland -version` is run in the final stage to confirm the binary is present and PATH resolution picks ours. **Why the latest-stable tag at build time and not a pinned version:** Valve's SteamOS `xorg-xwayland-24.1.9-1.1` is a stock upstream rebuild with zero Valve patches, so "match Valve" collapses to "match upstream stable". Pinning adds a maintenance burden (manual bumps when CVEs ship) without a corresponding benefit. Pulling at build time means each gow image rebuild picks up current Xwayland. **Why sed on a sentinel symbol, not a line-numbered diff:** line numbers in `xwayland-output.c` shift when upstream adds or moves code. A symbol-based transformation survives refactors as long as the array keeps its name. `xwl_output_fake_modes` has existed under that name since the feature landed in 2019. ### 2. Pin gamescope's internal resolution `apps/steam/build/scripts/startup.sh` and `images/base-app/build/scripts/launch-comp.sh` now pass `-w "${GAMESCOPE_WIDTH}" -h "${GAMESCOPE_HEIGHT}"` alongside the existing `-W/-H`. Orthogonal to the Xwayland patch: pins gamescope's nested Wayland output size so there's no room for a downstream component to round to a "standard" size. Matters for the future split-screen work where gamescope instances run at quarter-size (1128x752, etc.). ## What this unblocks Short term: users with 3:2 laptops, ultrawide monitors, or any non-16:9 native resolution get true full-screen rendering at their panel's actual size instead of a letterboxed approximation. Medium term: prerequisite for party-mode / split-screen streaming. In that architecture each player gets a per-tile gamescope instance at an arbitrary resolution (e.g. 2x2 of half-by-half dimensions). Without the Xwayland fix, every tile would round to the nearest standard mode and be letterboxed — making split-screen visually broken. ## Risks / caveats - **Legacy X11 apps that call `XRRSetScreenConfig()` to mode-switch** now have only one mode available and will no-op or error. In practice: gamescope handles this by rendering the app into its own window and scaling, which is the same outcome as before. No real regression. - **Xwayland desktop sessions** (XFCE et al.) see a single mode. Fine — they're window managers, not mode-switchers. - **Build-time dependency on `git ls-remote`** to freedesktop.org. If that site is unreachable at build time, CI fails. Acceptable failure mode; mirror can be added later if it becomes a problem. - **Upstream sentinel removal.** If Xwayland ever removes `xwl_output_fake_modes` (e.g. because they finally made the feature configurable), our sed finds nothing and the build fails with a clear message to refresh. This is the correct failure mode — we'd want to know and choose a new approach rather than silently building an unpatched binary. - **Image bake time.** Adds ~3-5 min to the base-app build (Xwayland compile). All descendants (Steam, Lutris, Heroic, ES-DE, XFCE, Firefox, Kodi, Pegasus, Prismlauncher, Retroarch) inherit the patched binary at no extra cost. ## Testing Not locally buildable in this environment (Proxmox LXC-docker shim doesn't support `docker build`). CI is the first real build — if meson fails or the sentinel assertions trip, it'll be visible in the workflow log. Once merged and an image is published, verification: - [ ] `Xwayland -version` inside any base-app-derived container runs and reports a version - [ ] On a 3:2 or ultrawide client, launch Steam with `RUN_GAMESCOPE=true` and `GAMESCOPE_MODE="-b -w $W -h $H"` — game's video settings should list only the native resolution, no standard-mode fallbacks - [ ] Moonlight stream overlay shows the native resolution end-to-end, no letterbox - [ ] Existing 16:9 users see no regression (native mode is 1920x1080 / 2560x1440 / etc. which the compositor was already advertising) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-15 12:50:37 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
games-on-whales/gow#293
No description provided.