The docs already told users where to get it — the Bazzite sysext, an Arch package, a NixOS option — and none of the three were true. `rpm.yml` built the sysext without ever passing `--gamescope`, `arch.yml` did not know the PKGBUILD existed, and the nix derivation had never been evaluated once. Evaluating it found its central assumption wrong: `gamescope.unwrapped` does not exist on current nixpkgs, where `gamescope` IS the buildable derivation, so the override threw on every build. It now prefers `.unwrapped` where a future nixpkgs wraps it and checks the RESULT is patchable — `overrideAttrs` on a symlinkJoin succeeds and does nothing, which would install an UNPATCHED gamescope under a name the host reads as a promise of HDR. Both it and the PKGBUILD had also drifted to a stale patch list: two patches named where three exist, one of them under the level-1 filename retired when the cursor patch landed. Both read the patch directory now, so the list cannot go stale again. The PKGBUILD additionally delegates the whole build to `build-punktfunk-gamescope.sh` rather than re-deriving the meson invocation — its copy had already lost `force_fallback_for=wlroots`, and unlike Fedora 43, Arch ships wlroots, so that package would have linked it shared and shipped a binary that starts only on machines carrying the dev library. It asserts its own pinned rev matches the script's, the one thing makepkg needs statically. Both CI builds are cached on `packaging/gamescope/**`, which is the only reason this is affordable: that tree depends on nothing else in the repo, so a normal push restores a binary instead of spending ten minutes on someone else's C++. And both are best-effort. punktfunk works without this binary — SDR on the gamescope backend, which is what every release before this one did — so a hiccup building gamescope must not cost the packages those workflows exist to publish. A failure warns and is never cached, so the next run retries. `rpm.yml` also installs Fedora's own gamescope for its runtime libraries: the sysext verifies our binary by executing `--version`, and on a cache hit nothing else in the job would have pulled libavif/luajit/seatd in. Verified by evaluating and patch-phase-building the nix derivation against nixos-unstable: all three patches apply to nixpkgs' already-patched 3.16.25 tree, and the banner stamps +pfhdr2. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
100 lines
5.2 KiB
Nix
100 lines
5.2 KiB
Nix
# `punktfunk-gamescope` — nixpkgs' gamescope carrying punktfunk's `pipewire-hdr` patches, exposed
|
|
# under its own name so it sits BESIDE the system gamescope instead of replacing it.
|
|
#
|
|
# An override rather than a from-scratch derivation on purpose: gamescope vendors wlroots,
|
|
# vkroots, libliftoff, libdisplay-info, SPIRV-Headers and reshade as git submodules plus two meson
|
|
# wraps, and nixpkgs already solves all of that. What we add is the patch set and a rename.
|
|
#
|
|
# This is also why the override needs none of the `force_fallback_for` armour
|
|
# `build-punktfunk-gamescope.sh` carries: that exists because meson silently prefers a SYSTEM
|
|
# wlroots when the build host has one and links it shared, producing a binary that starts only on
|
|
# machines with that dev library. A nix closure names every library it links, so the outcome is
|
|
# whatever nixpkgs' own gamescope already does — reproducibly.
|
|
#
|
|
# `gamescope` in nixpkgs is a wrapper (it wires the WSI layer + capabilities); the buildable
|
|
# derivation is `gamescope.unwrapped` — patching the wrapper would be a no-op, so this asserts on
|
|
# it rather than silently shipping an unpatched binary.
|
|
#
|
|
# Version drift: the patches are applied to whatever gamescope your nixpkgs pins, NOT to the
|
|
# commit `packaging/gamescope/build-punktfunk-gamescope.sh` names. Both hunks sit in code that has
|
|
# been stable across the 3.16 series (`src/pipewire.cpp`'s format builders, `paint_pipewire()` in
|
|
# `src/steamcompmgr.cpp`), so this normally just works — and when it does not, the build fails
|
|
# loudly at `patchPhase` rather than producing a gamescope that quietly cannot do HDR.
|
|
{
|
|
lib,
|
|
gamescope,
|
|
patchDir,
|
|
}:
|
|
let
|
|
# As of nixos-unstable (checked 2026-07-28) `gamescope` IS the buildable derivation — pname
|
|
# "gamescope", version 3.16.25, carrying `src`/`patches`/`mesonFlags`. Revisions that wrap it
|
|
# (to wire the WSI layer + capabilities) expose the build as `.unwrapped`, so prefer that where
|
|
# it exists and take `gamescope` itself otherwise.
|
|
#
|
|
# The check is on the RESULT, not on which attribute we found: `overrideAttrs` on a symlinkJoin
|
|
# wrapper succeeds and does nothing, which would hand us an UNPATCHED gamescope installed under
|
|
# our own name — the single worst outcome here, because the host reads the name as a promise of
|
|
# HDR. (`installCheckPhase` below greps for the marker as the second line of defence; this one
|
|
# fails at eval, before anything is built.)
|
|
base = gamescope.unwrapped or gamescope;
|
|
unwrapped =
|
|
if base ? src then
|
|
base
|
|
else
|
|
throw ''
|
|
punktfunk-gamescope needs a buildable gamescope derivation (one with a `src` that
|
|
`overrideAttrs` can patch); this nixpkgs' `gamescope` is neither that nor a wrapper
|
|
exposing `.unwrapped`. Update nixpkgs, or build the compositor with
|
|
packaging/gamescope/build-punktfunk-gamescope.sh instead.
|
|
'';
|
|
in
|
|
unwrapped.overrideAttrs (old: {
|
|
pname = "punktfunk-gamescope";
|
|
|
|
# Read the patch DIRECTORY rather than naming files: `builtins.attrNames` sorts
|
|
# lexicographically, which for `000N-` prefixes is exactly the apply order, and a patch added or
|
|
# renamed upstream of this file can no longer leave nix silently building a subset. (That already
|
|
# happened once — this list still named the level-1 banner patch after level 2 landed, so a nix
|
|
# build would have shipped a binary with no cursor patch and a marker claiming otherwise.)
|
|
patches =
|
|
(old.patches or [ ])
|
|
++ map (f: "${patchDir}/${f}") (
|
|
builtins.filter (lib.hasSuffix ".patch") (builtins.attrNames (builtins.readDir patchDir))
|
|
);
|
|
|
|
# nixpkgs builds from a `fetchFromGitHub` src, so there is no `.git` for `git describe` and the
|
|
# banner would read `+pfhdr2 (gcc …)` with no version at all — which the host's diagnostic
|
|
# version gate then misreads (it takes the first X.Y.Z triple it finds, i.e. the compiler's).
|
|
# Substituting the real version in keeps `--version` honest AND keeps our marker.
|
|
postPatch = (old.postPatch or "") + ''
|
|
substituteInPlace src/meson.build \
|
|
--replace-fail \
|
|
"vcs_tag = run_command(vcs_tag_cmd, check: false).stdout().strip()" \
|
|
"vcs_tag = '${old.version}'"
|
|
'';
|
|
|
|
# Ship ONLY the compositor, renamed. Everything else nixpkgs installs (gamescopectl,
|
|
# gamescopereaper, gamescopestream, the WSI layer, .desktop files) belongs to the real gamescope
|
|
# package — duplicating it here would put two of each on PATH. The host only execs the
|
|
# compositor.
|
|
postInstall = (old.postInstall or "") + ''
|
|
find $out -mindepth 1 -maxdepth 1 ! -name bin -exec rm -rf {} +
|
|
find $out/bin -mindepth 1 ! -name gamescope -delete
|
|
mv $out/bin/gamescope $out/bin/punktfunk-gamescope
|
|
'';
|
|
|
|
# `gamescope --version` exits non-zero on some builds; the grep is the real assertion.
|
|
doInstallCheck = true;
|
|
installCheckPhase = ''
|
|
runHook preInstallCheck
|
|
$out/bin/punktfunk-gamescope --version 2>&1 | grep -q '+pfhdr' \
|
|
|| { echo "punktfunk-gamescope: the +pfhdr marker is missing — the patches did not take"; exit 1; }
|
|
runHook postInstallCheck
|
|
'';
|
|
|
|
meta = (old.meta or { }) // {
|
|
description = "gamescope with 10-bit BT.2020/PQ PipeWire capture, for punktfunk HDR streaming";
|
|
mainProgram = "punktfunk-gamescope";
|
|
};
|
|
})
|