Feature Wayland, GameScope, Steam - #769
Merged
maxjivi05 merged 117 commits intoSep 23, 2026
Merged
Conversation
Port the embedded Wayland compositor (libbannerwayland) and the winewayland launch path written by Banner (The412Banner) for Bannerlator into WinNative. - Native: waylandcomp compositor with pre-generated protocol glue, vendored libwayland-server 1.26 and libffi, screen-effect shader headers, frame generation stub; JNI bound to com.winlator.cmod.runtime.display.wayland. New per-session begin/end on the long-lived compositor thread so a session can end and a new one start without killing the process, and a configurable session log directory. - Settings: Display Server (X11/Wayland) dropdown in the Display section of Container Settings and Shortcut Settings. Shortcuts follow the container until changed and saved; Wayland is only selectable when the device has an Adreno GPU and the selected Wine/Proton ships winewayland and the Wayland Turnip. Strings added for every locale; PaneNav handled by SettingDropdown. - Launch: display backend resolved per session with an X11 fallback and toast, compositor surface over the idle X view, X server input sink and pointer-lock relative mode, WinHandler relative mouse routing, clipboard and soft-keyboard text bridges, FPS limit and fullscreen stretch forwarding, FPS HUD feed, guest environment (WAYLAND_DISPLAY, XDG_RUNTIME_DIR, Proton lib path, Wayland Turnip variant by GPU, GALLIUM_THREAD=0), prefix registry driver selection with self-healing, winex11.drv disabled through WINEDLLOVERRIDES instead of renaming files. - Session lifecycle covers exit, forced cleanup, activity destruction and background session reattach. - Docs in docs/WAYLAND-DISPLAY.md; credit to The412Banner in CREDITS.md.
The Graphics tab now tells apart a non-Adreno device from a Proton that lacks winewayland and the Wayland Turnip, names Banner's Wayland Proton layer as the fix, and no longer cuts the explanation off after three lines. The doc points at the stock Proton as the reason the dropdown stays on X11.
…les into any arm64ec Proton A Wayland launch hung at Starting Wine because nothing created the compositor's runtime directory, so wayland-0 was never bound and winewayland waited forever. The session now makes the directory before the compositor starts, and the guest launcher extracts the xkb keymap and waits up to eight seconds for the socket before exec. An arm64ec Proton that lacks winewayland can now borrow the driver, the Wayland Turnips, the client libraries and the xkb data from an installed Wayland Proton of the same Wine major version. The Display Server row offers Wayland and names the donor, saving Wayland runs the copy on a worker thread with a toast on completion, and a launch that still finds the files missing starts the copy and runs on X11 that time. Files land through temp names so a half copy never reads as capable.
…ng at startup Seeding HKCU Software\Wine\Explorer Desktop=shell made every process of the session start on that desktop, so a second explorer raced the launcher for the desktop window. The loser's window creation was aborted by WM_NCCREATE, Wine's explorer never entered its desktop message loop, it returned immediately, and the guest wrapper exiting tore the whole session down at Starting Wine. Device traces show the same launch on X11 creating the window and entering the loop. Only the Desktops\shell size entry is written now; the desktop window is created, the message loop runs and the session stays alive. Also on Wayland: the X server is no longer started, a stale prefix is brought up to date before the session instead of blocking explorer behind wineboot, and the launch overlay is cleared on a timer since no first frame arrives for a desktop that only presents shm. Turnip manifests are written under WinNative's own wayland_turnip[_variant].json names and winewayland is pointed at the chosen one, so no donor branding is relied on.
…on file copy A shortcut or container left on the System graphics driver started the compositor on the stock Vulkan driver, which has no VK_EXT_image_drm_format_modifier, so vkCreateDevice failed and the session presented nothing. The compositor driver now falls back to the container's own driver and then to any installed one, and says which it picked. Copying winewayland.so into a Proton that does not ship it is removed. It is a Wine unixlib bound to the win32u it was built against: the borrowed driver loads, connects to the compositor and enumerates its outputs, but creates no window, so the session ran with a black screen and nothing in the log said why. A Proton now has to ship its own Wayland driver, which the Display Server row already states. Device-proven on an Adreno 840: NieR:Automata runs on the Wayland layer at around 59 fps with its window on the compositor's virtual desktop.
…r the session never uses
winewayland.so in an x86_64 layer needs x86_64 Wayland, xkb and their transitive libraries; the container image only has aarch64 copies, so the layer vendors its own. Shipping them in the Proton's lib/ put x86_64 libc++_shared.so and libz.so.1 ahead of the image for the native box64 binary, which then refused to link and the session ended with no window. They now live in lib/wayland-x86_64/, which only Box64 is pointed at. The Bionic launcher's call is also moved after the two library paths it was being overwritten by.
An x86_64 layer's winewayland.so runs fine under Box64 and drives the compositor, but it has to dlopen the Wayland Turnip to give the game its ICD and that Turnip is aarch64, so the pin fails and the session is a desktop that never presents a frame. Binding the driver to Box64's native libwayland-client.so.0 wrapper instead shares one connection but faults at the entry to the driver's event thread. The x86_64-unix fallback in the capability check is dropped, so those layers keep the Display Server control on X11, where DXVK reaches the GPU through Box64's Vulkan wrapper.
The Wayland backend compiled a stub for its frame-generation engines, so the compositor reported both engines unavailable and never generated a frame. Build the X11 renderer's own engines into libwnwayland instead and drive them from the compositor's Turnip device: - framegen_engine.c implements the fge_* contract over vkr_lsfg and vkr_dis. Their sources are compiled into the compositor rather than linked from libwinlator, because each library keeps its own VkDispatch; -fvisibility=hidden keeps the two copies apart. - vkd_bind_proc() binds VkDispatch from a vkGetInstanceProcAddr the caller already holds, which is how the compositor reaches Turnip through adrenotools. - The bridge's engine tuning took Win-FG's model/preset pair and clamped DIS's flow resolution and target rate into it, which turned 180/120 into 4/2 and planned nothing. It now carries both values through. - The generation ring was sized to multiplier-1, capping Wayland at 2x while X11 reached the configured target rate. With a target rate set, size it to the engine maximum and let the pacer choose, as X11 does. - frameGenRefreshRate was never assigned on the DIS path, so the pacer saw a 0 Hz panel. - The HUD's output-frame source and the system frame-generation monitor read the compositor's cumulative counters in Wayland mode; the X11 renderer holds none there. Also drop the upstream project's name from source filenames and identifiers - it belongs in CREDITS.md, not in our tree. The banner_desktop_v1 and banner_ahb_v1 interface names and the BANNER_WAYLAND_* environment variables stay: the shipped winewayland.so binds those strings, and renaming them would break every existing Wayland Proton layer. Verified on device with Monster Hunter Stories at 30 fps, target 120: LSFG and DIS each reach 120 fps on both Wayland and X11.
The HUD showed "Vulkan" for every Wayland session, whatever the game ran on. It reads the name from an X11 window property that the Vulkan wrapper sets on the game's window, and a Wayland session has neither half of that: the game never makes an X11 window, and winewayland.drv repoints VK_ICD_FILENAMES at the bundled Turnip, so the wrapper is not even loaded. The field kept its "Vulkan" initialiser for the whole session. The container's own processes carry the same answer - whichever translation layer the game runs on is mapped into it - so read it from /proc instead, ranked so the highest-level layer wins: a VKD3D title maps dxgi as well, and a store front end maps its own Direct3D before the game maps its. Polling continues after the first answer so that second case corrects itself. The probe runs on its own thread; a tick reads about a megabyte of /proc. It touches the HUD only through the UI thread, and only when the name actually changes. The Mango HUD's engine label comes off the same field, so it follows. Verified on device: Monster Hunter Stories on Wayland now reads DXVK, where it read Vulkan before.
Switching a shortcut from a container on an x86_64 Proton to one on an arm64ec Proton left the Wayland row greyed out with the "needs a Wayland Proton" text until the sheet was saved and reopened. The row keys its capability check on state.wineVersionIdentifier, and handleContainerChanged updated isArm64EC and the version display but not the identifier, so the check never re-ran and kept the old container's verdict. Move the identifier with the container, as loadInitialData does. The identifier has no other reader, so nothing else changes.
A game presenting through the compositor drew far more frames than reached the screen: 1780 in ten seconds for 908 shown, measured on a session log. A swapchain that does not wait for frame callbacks is paced only by how fast its buffers come back, and a replaced buffer went back the moment it was replaced, so the game ran as fast as the GPU allowed and everything above the refresh rate was thrown away. X11 never did that - there a buffer returns only once the scene using it has been composed. Release a replaced buffer on the screen's cadence when the session has no frame rate limit of its own, through the limiter's existing schedule. The same session now produces 1076 frames for 1010 on screen. Two more things a Wayland session did that an X11 one did not: The vsync ticks ran on after the output surface was gone, so a backgrounded session woke the UI thread and the compositor thread at the panel's refresh rate to draw on a screen it no longer had. X11 parks its render thread at the same point; stop the ticks with the surface. The renderer the HUD names was found by reading every process on the device every couple of seconds. The compositor already reports the process behind the window it is showing, so read that one instead, and keep the sweep only for when there is no pid to go on.
Two orphaned helper processes were found on a test device with three threads spinning at 100%, holding 3.2 GB, having survived every session teardown for the better part of seven hours - 20.9 CPU-hours between them and a load average of 19 on an idle device. The session sweep matches a process against a list of Windows executable names, over its stat line and its command line together. A Chromium helper (Steam's web helper is one) clears its own argv, so the command line is empty and the only mark it carries is its name, which is not a Windows executable name. Nothing in the list could match, so the sweep never saw these processes at all, let alone signalled them. Match those names too, for a process of this app's own uid and not this process. The sweep walks all of /proc, so a name on its own is not enough: a foreign process cannot be signalled, and terminateSessionProcessesAndWait waits while its list is non-empty, so one such match would spend the whole timeout on every teardown. WebView's renderers carry Chromium's names as well and are excluded by the same test, running as they do under an isolated uid. The web helper is listed as a core process so background pausing leaves it running; its name joins that list too, now that the sweep can see it.
The Add dialog now takes any Windows or Linux executable (.AppImage, .sh, .run, .bin, .elf and the .x86_64/.aarch64 build suffixes) alongside console ROMs, and asks whether the entry is a game or an application. The answer is stored as the library_type extra and the Games / Applications filters honour it; custom entries previously ignored the filters altogether. Linux entries are written with runtime=linux and Exec=linux:native. There is no Linux runtime yet, so starting one reports that instead of handing the path to Wine. docs/GAMESCOPE.md records what gamescope is, what the compositor already provides for its Wayland backend, and the phases to a Linux runtime and gamescope session. The hard-coded English in the dialog and its toasts moved to resources, translated for every locale.
Its display is an unprivileged app over SurfaceControl; root is only for the chroot rootfs. gamescope runs nested in a small dma-buf-only Wayland compositor with Android input bridged as wl_seat events, which is the shape this plan already has. The launch command, env and the native arm64 Steam path are recorded so Phase 2 and 3 do not rediscover them.
Container Settings and Shortcut Settings gain a Runtime row: Wine, or Gamescope, stored as the runtime extra with the same per-shortcut override as Display Server. A Gamescope container starts the Wayland compositor and, in place of Wine, execs proot over files/linuxfs running gamescope --backend wayland --expose-wayland as a client of it, with the session script deciding what gamescope hosts: the file manager from Edit Containers, a Linux program from a library entry, or the native arm64 Steam client for a Steam entry. Windows entries are refused with a translated message. A background session records that it is a Linux one so a reattach resolves to gamescope. proot (the Termux-derived tree already in cpp/) joins the CMake build as libproot.so and libproot-loader.so. The loader is linked freestanding at a high image base, statx is path-translated, and PROOT_NO_SECCOMP disables the seccomp accelerator for debugging. tools/linuxfs/build-linuxfs.sh assembles the runtime from Arch Linux ARM packages (gamescope 3.16.29, Xwayland, Mesa with Turnip and Zink, pcmanfm) plus the session scripts and the fake /proc files Android denies. It ships no Valve software; winnative-steam-install fetches the native arm64 client from Valve's client-update manifest at first use. gamescope refuses a wl_seat older than 8, so the compositor advertises 9 and sends axis_value120 to version 8+ pointers. Verified on the build machine: the full launch line runs from the rootfs under qemu-user as a client of a nested sway up to gamescope's Vulkan allocation, which lavapipe under emulation cannot satisfy.
…client Verified on the NP06J (Adreno 840, Android 16): gamescope, Xwayland with glamor on Zink and the Steam arm64 client all run inside the proot rootfs as the app's own uid, and the Steam client updates itself and reaches steamwebhelper. - proot: emulate set*uid/gid and setfs*id under the app seccomp policy (Xwayland's xkbcomp and the X access control need them). - compositor: xdg_toplevel fullscreen configure (gamescope drops its libdecor frame), wl_output name, and seat events to every pointer and keyboard object of a client (gamescope reads input on a second pair). - Turnip: cross-built Mesa 26.2.2 with the KGSL backend, reporting the KGSL device as its DRM node so gamescope offers linux-dmabuf and the WSI agrees on a main device (build-turnip.sh, turnip/*.patch). - LinuxRuntime: present /dev/kgsl-3d0 as /dev/dri/renderD<n> with the sysfs entries libdrm reads; passwd/group for the app uid; fake overflowuid/gid for glycin's sandbox. - libwnsession.so, preloaded into the session: System V IPC, GEM handles for the KGSL node, get_robust_list for Steam's cross-process mutex, and loopback port ownership for Steam's lsof peer check. - rootfs: GTK 2 from Debian for steamui.so, openal, libvdpau, lsof, DejaVu fonts, pacman hook equivalents; Steam gets the ~/.steam links steam.sh makes and restarts on exit code 42.
The client splits lsof's address on "->" and drops the record unless it gets two halves, so the single-address answer left it with no peer pid and it refused steamwebhelper's websocket (Checked: 0/<pid>). Record the far end of every loopback socket and print source->destination the way lsof does; posix_spawn reports a failed fork instead of success with no pid.
execve of a script used to reach the kernel as-is and fail with ENOEXEC unless bash's fallback caught it; Steam's compat tools are python scripts and need argv rewritten the way the kernel does it, up to four interpreter levels.
The Linux runtime now preloads the fake evdev layer built against glibc, with the input rings bound in as /dev/input, so Steam and SDL see the on-screen and physical controllers as evdev gamepads. Games downloaded through the app are presented to the native client as a second library folder at /mnt/winnative: each install directory is bound in under its Steam name, its manifest copied alongside, and winnative-steam-library registers the folder in steamapps/libraryfolders.vdf. Steam's arm64 launcher also wants the sdkarm64 and binarm64 links.
The runtime is no longer a per-container dropdown that a save could silently rewrite; instead Settings > Containers has its own GameScope section that creates one Wayland container, and the library carries a Steam entry that boots into it. Edit Container and Shortcut Settings keep the display-server choice but drop the runtime row, and the GameScope container is hidden from the setup wizard's Wine checks.
execveat(2) was absent from the seccomp trap table, so the tracer never saw it and guest calls fell through to the kernel; FEX launches every emulated child that way, which crashed x86 processes that spawned others (wine starting wineserver). Trap it, and at enter rewrite it into the execve the loader path already handles: the target becomes SYSARG_1 (named through /proc/self/fd for an fd-relative call, then resolved to the real path), argv and envp shift down from SYSARG_3/4. A program run as /proc/self/fd/N or by execveat now maps correctly.
The kernel here has no user namespaces, so pressure-vessel cannot build the container Proton normally runs in. Instead winnative-fex-rootfs completes the Steam Linux Runtime platform tree in place (its mtree names the symlinks and deduplicated objects the depot ships unpacked) and uses it as the x86_64 root FEX emulates against, and winnative-proton is registered with the client as a compatibility tool that runs Proton's own script there, with Vulkan and GL thunked to the host drivers. Verified: Proton builds a full prefix and wine starts its child processes.
The entry showed the generic placeholder: the icon was written only when the shortcut was first created, and a square image is cropped by the wide card. Render both sizes whenever the entry is ensured, and give the card a banner- shaped cover with the glyph centred on the client's own dark ground.
Two things kept Windows games from starting in the GameScope session. The app sandbox's seccomp filter answers openat2() and faccessat2() with ENOSYS, and a filter returning an errno outranks proot's trace action, so the tracer never sees the call and cannot answer it either. FEX resolves every guest path with openat2(rootfs_fd, path, RESOLVE_IN_ROOT); denied that, it falls back to the host's own root and the guest loader finds none of its libraries. libwnopenat2.so, preloaded into the FEX processes alone, answers both with the syscalls the filter does allow, walking a rooted resolution component by component so that an absolute symlink inside the runtime tree lands back at that tree's root. The Steamworks stub a game loads is then the second half: Proton's lsteamclient runs emulated and dlopens ~/.steam/sdk64/steamclient.so, which pointed at the arm64 client's tree. An x86_64 process cannot map an AArch64 library, and Steamworks aborted on it. Valve's client ships all three builds, so each SDK path now names its own architecture; the stub is only an IPC shim to the running client, which is free to stay arm64. Run-as processes carry no seccomp filter, so neither fault reproduces in the test rig; both were confirmed by installing the filter there by hand.
The client died a minute or two into every session with systemd's safe_fclose() asserting on EBADF, taking the running game down with it. The backtrace puts it in libudev, reached from SDL's controller discovery by way of libusb: the sandbox denies the NETLINK_KOBJECT_UEVENT socket every monitor is built on, and libudev unwinds from that through a path that closes a descriptor a stream still owns. SDL asks often enough - fourteen thousand times in two minutes on one thread - that hitting it is a matter of when. Answering with the NULL libudev would have returned keeps the caller on the failure path it already handles. SDL loads udev through dlopen() and dlsym() rather than the linker, so the library itself is refused for SDL as well, which is the same answer a system without udev gives; SDL then reads /dev/input directly, which is where this session's controllers are. The socket is tried first so both answers stay out of the way wherever a monitor could really be created, and no other caller is touched - the client's web helper loads udev for its own device enumeration and does not survive being told it is missing.
… seccomp off The Poco F6 that cannot start a session fails identically on the build that carried the fallback, and Bannerlator saw the same three errors from this proot tree with PROOT_NO_SECCOMP=1 as without. The probe cost every session a proot start for nothing. The dialog for a session that dies at once stays.
Android's allocator tags heap pointers in their top byte, and the first execve is made by proot's own child with its path and environment on that heap. A verbose trace from a Poco F6 (kernel 6.1.138) shows the call entering with 0xb400007b80852f10 and being answered EFAULT: that kernel does not read a tagged address on a tracer's behalf, so no program ever started. Guest addresses carry no tag, so masking it changes nothing for them.
Every path proot translates was read with a ptrace call per eight bytes and written back the same way. process_vm_readv and process_vm_writev do it in one, as upstream proot does; what the kernel refuses still goes through ptrace, which can force a read-only page and reports the error.
-r was only passed with a limit, so without one gamescope said 60 Hz and games offered and capped themselves at 60 on a faster panel. From Bannerlator ec93d55.
maxjivi05
force-pushed
the
feature/wayland-gamescope
branch
2 times, most recently
from
September 22, 2026 02:13
8ce3f27 to
3eb5968
Compare
maxjivi05
force-pushed
the
feature/wayland-gamescope
branch
from
September 22, 2026 02:17
3eb5968 to
dddaa6f
Compare
A Linux session died the moment it started, on every device: the command line carried -i <uid>:<uid>, an option upstream proot takes and this tree does not, and an option it does not know is fatal before any session runs. Three users' logs are the same three lines. Identity needs no option here. setuid, setgid, setresuid, setresgid, setreuid, setregid and setgroups are answered in tracee/seccomp.c, which Android reaches through its own SIGSYS and through the ENOSYS restart in syscall/exit.c, so it is not in proot's trace filter and does not belong there. The check that was meant to cover this ran the host's proot, which does have -i, so it passed while validating a binary that is not the one shipped. It is replaced by one that runs the packaged binary with the real option list.
Steam mode started a heartbeat in the background and never took it down. It outlives the script by up to an hour, and on the way out gamescope's reaper adopts it and waits for it; gamescope is proot's root tracee, so proot never exits, waitFor never returns, and the screen stays black with no way back to the library. The sweep afterwards is no second chance: it matches processes already handed to the reaper, and a job this shell still owns is not one of them. Every background job is now killed and waited for before it runs. Ending a session goes straight to the kill again. Asking proot first does nothing, since it answers SIG_IGN to every signal but the fatal few, and its tracees are traced with PTRACE_O_EXITKILL, so the kernel takes gamescope and everything under it down with proot. Waiting on a thread of its own only let the exit path reach killProcess first and leave that tree running.
Only a session that died inside twenty seconds was reported; anything later closed the screen without a word, which from the outside is a crash, and left the one person who could send the log with no reason to look for it. Every end nobody asked for is now said. A status of 137 names Android's limit on background processes, the likeliest cause of a death mid-session, under the same name the setup wizard uses for the switch that lifts it. The log says what it was asked and how it ended: the build that wrote it, proot's own options, and a signal named rather than left as a number, which 137 otherwise reads as an exit code the session chose. The guest command is left out, since the container's environment rides in it. An alert has nothing to cancel, so back or a tap outside now means what OK means. Without it the callback was skipped and this dialog left the activity alive over a session that was already gone.
The Steam client records the token that signs its account in to its own connection log, and Logs Manager hands those files to whoever the user sends them to. Every way out - the archive, Share and Download - now goes through one copy that takes it out, and the app's own logs are copied unchanged. Share writes to a directory of its own, emptied first, so two logs of the same name cannot be confused for each other.
Android kills an app's background child processes past thirty-two, and a session runs well past that: killing proot ends it. It is said once, when the Linux client download starts, because that is when the limit begins to matter and there are minutes of download to act in. Android 14 and later carry the switch in Developer options and are offered it; earlier releases get the adb line, since only adb can set it there.
The driver an entry runs on falls back to the one the Drivers page calls active when nothing is saved, and to the bundled Mesa when a saved name is gone, rather than to whichever driver happened to be first. The game's layer is posted with the cadence the session is aiming for - the frame limit when one is set, otherwise the panel's rate. It had been posted with a vote of zero every session, which leaves the system to infer the cadence from the rate already being achieved; on a phone whose vendor picks clocks from that, a session that has been slowed reads as one that wants to be. A Proton download checks for room before it starts and clears a half-finished tree, and PROTON_USE_XALIA is one of the known variables so Proton's own gate is reachable without typing the name.
The update check only ever knew the official releases, and only from a build whose version parsed and whose signature was the release one. A build from CI is neither, so anyone running one was never told a newer one existed. A pull request build now follows its own pull request. CI stamps the build with the pull request it came from and the commit the release notes name, so the install knows which of the CI releases is its own: pr-769 is offered to 769 and to nothing else, and 770 to 770. The tag never moves over a pull request's life and the assets under it are replaced, so there is no version to compare - the build under the tag is a different one when its commit is not the one installed. The APK taken is the one matching this build's flavour, as it already was for official releases. A pull request that has been merged or closed takes its release with it, and that reads as nothing to install rather than as a failure. Which releases a build follows is no longer a setting. The two are signed with different keys, so neither could be installed over the other, and offering the choice only invited a download that install would refuse. Development went with it: it offered pre-releases of the official repository, and there are none.
The store services are START_STICKY, so Android recreated them with a null intent after any process death. Each one registered the keep-alive component before looking at the intent, which pinned a foreground service and a partial wakelock with no session, no UI and nobody asking: measured at 5h 2m of foreground service against 13m 34s of real use, and a wakelock held 8h 39m across ten spans because the screen-off receiver took it without checking whether a session was running. Background chat is the one caller that needs the restart - stopManagedServices leaves SteamService started while onTaskRemoved kills the process - so Steam refuses only when that setting is off and there is no login to return to.
libpci chooses its procfs backend on whether it can read the /proc/bus/pci directory, which the app can, and then opens the devices file inside it, which Android denies. Its error path is die(), so the process calling it exits(1). Chromium loads libpci in its GPU process to name the video card, so the GPU process died six times at every startup - exit_code=256 against six "pcilib: Cannot open /proc/bus/pci/devices" lines, interleaved one to one - and what came back was slow enough to halve the Steam UI. Binding an empty file over it answers the scan truthfully: nothing the app can see is on a PCI bus. The file is created at session start rather than shipped in the rootfs overlay so installs that already have a rootfs are fixed too, and the table's existing guard binds it only when the real file cannot be read, so it can never stand in front of real data. Measured on a OnePlus 15, scrolling the Big Picture library: 44 fps before, 85 after, against a gamescope asked for 90, with the GPU process crash count going from twelve to zero.
GE-Proton and CachyOS Proton import protonfixes from the top of their proton script, and its esync check opens /proc/sys/fs/file-max with nothing catching a failure. Android denies that file to an app uid, so the PermissionError came out of the import and ended the launch before the game started - Steam put the Play button back with no window and no clue, for every game on either build. file-max joins the /proc stand-ins the session already binds. protonfixes only warns below 8192, so what matters is that the read succeeds. Both it and pci_devices are written at session start rather than shipped in the rootfs overlay, because an install that already has a rootfs would never receive them. Verified on a OnePlus 15 with Among Us: on GE-Proton11-7-aarch64 protonfixes goes from PermissionError to "All checks successful" and the game reaches its menu at 60 fps. CachyOS 11.0-20260703 clears the same check and brings Unity up with its full thread pool, but still parks before it presents a window - a separate fault, not this one.
The Linux Client dialog carried a "Linux Proton builds" link to a second download screen for the same GE and CachyOS builds that Components already lists, installs and removes through the same LinuxProtons calls. The dialog was that link's only entry point, so it goes with it, along with the four strings that only it used. Components had no way to stop an in-flight download, which the dialog did, and these builds are several hundred megabytes; a mistaken tap could otherwise only be undone by killing the app. So the cancel moves to the component card rather than being lost with the dialog.
CachyOS turns on its PipeWire driver for every game, and with no PipeWire server in the session the game hung at audio init behind Steam's spinner. The launcher now defaults PROTON_USE_PIPEWIRE to 0, leaving an explicit setting alone; no other Proton reads it.
Every PR comes from a fork, and fork runs get no secrets, so PR CI signed each build with a freshly generated key. The publish run holds secrets without running PR code; it now re-signs the APKs with a key kept for PR builds (PR_KEYSTORE_BASE64, PR_KEYSTORE_PASSWORD, PR_KEY_ALIAS, PR_KEY_PASSWORD) and warns when that key is not configured.
The winnative-proton tool now runs the Proton Experimental build that ships with WinNative before any ARM64 depot Steam installed, and Steam lists it under that name.
Advanced in a GameScope container, and in a game's shortcut, now offers WN Proton Experimental and the Proton builds downloaded under Components. The container's choice is the default until a shortcut sets its own, like the other settings. A game's choice is kept in step with its Compatibility setting in Steam, whichever was changed last: a change made in Steam shows in the shortcut, and one made in WinNative is written into Steam's mapping when the client is not running. Until then winnative-proton-launch reads the choice as the game starts, so it applies to a running client too.
…river The runtime's Turnip gets the device-table fixes WN's Android drivers ship: early preamble and scalar predicates off on A725/A730, the TP UBWC flag hint on A740/X1-85, GMEM off on A810/A829, the KGSL ids A810/A829 report, and an A825 entry. A840, A750 and every other GPU build unchanged. A GameScope container's driver setting names its Linux Turnip, so the compositor took whichever Android Turnip was listed first. It now prefers the WN Turnip the Linux Client install fetched, and falls back as before.
A session draws with the driver of the entry that started it, and every game the Steam client started inherited that one, so a game's own Graphics driver was ignored whenever it was started from Big Picture or the Steam entry. The app now records each game's driver - its shortcut's, else the container's - and winnative-proton-launch sets it as the game starts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.