forked from unom/punktfunk
fix(packaging): every channel ships the WSI layer, so in-game HDR works off a stock install
The previous commit built the layer and taught the host to use it, but only the
Arch PKGBUILD carried the files, so every other channel still landed on the
no-game-HDR fallback. This finishes the job.
The packaging scripts now take `--stage`, the DESTDIR the gamescope build script
wrote, instead of a path to one binary. That is the part worth keeping: the next
file this package needs will not require a new flag in four scripts and two
workflows. CI caches the whole staged tree for the same reason. The gs-cache key
already hashes packaging/gamescope/**, which this commit changes, so stale caches
in the old single-file shape cannot be restored into the new layout.
Channels, all of them:
rpm spec gains Source1/Source2 and %files entries
deb build-gamescope-deb.sh copies the layer into the package root
Arch PKGBUILD (previous commit); the sysext extracts the whole usr tree
sysext bazzite takes --gamescope-stage; arch asserts the layer arrived
nix the derivation keeps, renames and rewrites the layer rather than
deleting it with everything else
A missing layer is fatal in every one of them, not best-effort. A package that
carries the compositor without it looks completely healthy and then silently
denies every game an HDR10 swapchain -- the exact failure this whole change
exists to end, so it must not be possible to ship it again by accident.
Two things needed care:
The layer manifest carries an ABSOLUTE library_path baked in at build time, so
every channel has to install the .so at exactly that path. That means literal
/usr/lib/punktfunk, not %{_libdir} (which is /usr/lib64 on Fedora) and not a
Debian multiarch triplet. Nothing links the .so by soname -- the loader dlopens
it by that path -- so multilib has no claim here. The rpm and nix install checks
now read the path back out of the manifest and fail if it names a file the
package does not install, because a manifest pointing at nothing is the silent
shape of this bug.
NixOS has no /usr, so the layer lives inside the gamescope derivation and the
host's path is overridable via PUNKTFUNK_GAMESCOPE_WSI_LAYER_DIR, which the
module sets -- the same posture as PUNKTFUNK_GAMESCOPE_BIN, and documented.
The manifest rewrite moved out of a heredoc into
packaging/gamescope/rewrite-wsi-layer-manifest.py because the FHS builds and the
Nix store both need it and must rename the layer identically; two copies would
drift into a host looking for a name only one of them produces.
Verified: 214 pf-vdisplay tests pass in a linux container, clippy -D warnings and
rustfmt clean, bash -n on all five changed shell scripts, both workflow YAMLs
parse, and the rewrite script was run against a synthetic FROG manifest to
confirm it renames/repoints/regates while preserving the `functions` block --
which is the field that decides whether the layer loads at all.
NOT verified: no nix on this machine, so gamescope.nix, flake.nix and the module
are unevaluated; no gamescope build, no package build of any kind, and no game
has taken an HDR swapchain on glass.
This commit is contained in:
@@ -517,15 +517,17 @@ jobs:
|
||||
bash packaging/gamescope/build-punktfunk-gamescope.sh \
|
||||
--destdir "$PWD/gs-stage" --prefix /usr --jobs "$(nproc)" \
|
||||
--extra-fallback libdisplay-info
|
||||
install -Dm0755 gs-stage/usr/bin/punktfunk-gamescope gs-cache/punktfunk-gamescope
|
||||
# The WHOLE staged tree, not just the binary: it also carries the Vulkan WSI layer built
|
||||
# beside the compositor, which is the only route to an HDR10 swapchain for a nested game.
|
||||
mkdir -p gs-cache && cp -a gs-stage/. gs-cache/
|
||||
|
||||
# The binary must RUN, not merely link: `--version` is what the old job used as its ship
|
||||
# gate, and it is the cheapest proof that the static-libstdc++ trick and the vendored wlroots
|
||||
# actually produced a working compositor.
|
||||
- name: Build the .deb
|
||||
run: |
|
||||
gs-cache/punktfunk-gamescope --version
|
||||
bash packaging/debian/build-gamescope-deb.sh --binary gs-cache/punktfunk-gamescope
|
||||
gs-cache/usr/bin/punktfunk-gamescope --version
|
||||
bash packaging/debian/build-gamescope-deb.sh --stage gs-cache
|
||||
|
||||
- name: Publish to the Gitea apt registry
|
||||
env:
|
||||
|
||||
@@ -293,7 +293,11 @@ jobs:
|
||||
dnf -y install wayland-protocols-devel glm-devel cmake libXcursor-devel || true
|
||||
if bash packaging/gamescope/build-punktfunk-gamescope.sh \
|
||||
--destdir "$PWD/gs-stage" --prefix /usr --jobs "$(nproc)"; then
|
||||
install -Dm0755 gs-stage/usr/bin/punktfunk-gamescope gs-cache/punktfunk-gamescope
|
||||
# The WHOLE staged tree, not just the binary: it also carries the Vulkan WSI layer built
|
||||
# beside the compositor, which is the only thing that can give a nested game an HDR10
|
||||
# swapchain. Caching the tree rather than a file per artifact is what keeps the next
|
||||
# addition from needing a change here as well as in every packaging script.
|
||||
mkdir -p gs-cache && cp -a gs-stage/. gs-cache/
|
||||
else
|
||||
# Warn only, even on a tag — the hard gate is the LAST step of this job. Failing here
|
||||
# would skip the sysext build, the sysext feed, AND the release attach below, so a
|
||||
@@ -311,9 +315,9 @@ jobs:
|
||||
# existing SDR/host-composited path. The spec re-checks the +pfhdr marker itself.
|
||||
- name: Package punktfunk-gamescope as an RPM
|
||||
run: |
|
||||
if [ -x gs-cache/punktfunk-gamescope ] && gs-cache/punktfunk-gamescope --version >/dev/null 2>&1; then
|
||||
if [ -x gs-cache/usr/bin/punktfunk-gamescope ] && gs-cache/usr/bin/punktfunk-gamescope --version >/dev/null 2>&1; then
|
||||
bash packaging/gamescope/build-gamescope-rpm.sh \
|
||||
--binary gs-cache/punktfunk-gamescope \
|
||||
--stage gs-cache \
|
||||
--release "$PF_RELEASE"
|
||||
else
|
||||
# Warn only — see the note on the build step. The gate is the last step of this job.
|
||||
@@ -372,9 +376,9 @@ jobs:
|
||||
# whose runtime libs are missing from this container must cost the image its HDR, not the
|
||||
# image itself.
|
||||
gs=()
|
||||
if [ -x gs-cache/punktfunk-gamescope ] && gs-cache/punktfunk-gamescope --version >/dev/null 2>&1; then
|
||||
gs=(--gamescope gs-cache/punktfunk-gamescope)
|
||||
echo "folding in $(gs-cache/punktfunk-gamescope --version 2>&1 | head -1)"
|
||||
if [ -x gs-cache/usr/bin/punktfunk-gamescope ] && gs-cache/usr/bin/punktfunk-gamescope --version >/dev/null 2>&1; then
|
||||
gs=(--gamescope-stage gs-cache)
|
||||
echo "folding in $(gs-cache/usr/bin/punktfunk-gamescope --version 2>&1 | head -1)"
|
||||
else
|
||||
echo "::warning::no usable punktfunk-gamescope for f${{ matrix.fedver }} — the sysext ships without it (gamescope sessions stay SDR)"
|
||||
fi
|
||||
|
||||
@@ -4218,9 +4218,19 @@ const WSI_OFF_ENV: [(&str, &str); 2] = [
|
||||
/// the compositor cannot disagree about `gamescope_swapchain` — which is what makes every "is the
|
||||
/// distro's layer close enough to ours?" guess unnecessary. It carries its own layer name and its
|
||||
/// own `enable_environment`, so it coexists with the distro's rather than replacing it.
|
||||
const OUR_WSI_LAYER_DIR: &str = "/usr/lib/punktfunk/vulkan/implicit_layer.d";
|
||||
const OUR_WSI_LAYER_MANIFEST: &str =
|
||||
"/usr/lib/punktfunk/vulkan/implicit_layer.d/punktfunk_gamescope_wsi.json";
|
||||
const OUR_WSI_LAYER_DIR_DEFAULT: &str = "/usr/lib/punktfunk/vulkan/implicit_layer.d";
|
||||
const OUR_WSI_LAYER_MANIFEST_NAME: &str = "punktfunk_gamescope_wsi.json";
|
||||
|
||||
/// Where our layer's manifest directory is. FHS by default, because that is where every distro
|
||||
/// package puts it; `PUNKTFUNK_GAMESCOPE_WSI_LAYER_DIR` overrides for a store with no `/usr` to
|
||||
/// speak of — on NixOS the layer lives inside the gamescope derivation and the module points this
|
||||
/// at it, the same posture as `PUNKTFUNK_GAMESCOPE_BIN`.
|
||||
fn our_wsi_layer_dir() -> String {
|
||||
std::env::var("PUNKTFUNK_GAMESCOPE_WSI_LAYER_DIR")
|
||||
.ok()
|
||||
.filter(|v| !v.trim().is_empty())
|
||||
.unwrap_or_else(|| OUR_WSI_LAYER_DIR_DEFAULT.to_string())
|
||||
}
|
||||
|
||||
/// Which Vulkan WSI layer a session we spawn should run with. Three states, decided ONCE per
|
||||
/// launch because [`WsiPlan::resolve`] can spawn `--version` probes.
|
||||
@@ -4240,7 +4250,8 @@ impl WsiPlan {
|
||||
/// ⚠️ Spawns up to two `gamescope --version` probes in the fallback arms, so resolve once and
|
||||
/// pass the result around rather than calling this per use site.
|
||||
fn resolve() -> Self {
|
||||
if std::path::Path::new(OUR_WSI_LAYER_MANIFEST).is_file() {
|
||||
let manifest = std::path::Path::new(&our_wsi_layer_dir()).join(OUR_WSI_LAYER_MANIFEST_NAME);
|
||||
if manifest.is_file() {
|
||||
Self::Ours
|
||||
} else if wsi_layer_matches_our_gamescope() {
|
||||
Self::DistroKept
|
||||
@@ -4250,20 +4261,23 @@ impl WsiPlan {
|
||||
}
|
||||
|
||||
/// The environment this plan needs, as `(name, value)` pairs.
|
||||
fn env(self) -> Vec<(&'static str, &'static str)> {
|
||||
fn env(self) -> Vec<(&'static str, String)> {
|
||||
match self {
|
||||
// `VK_ADD_IMPLICIT_LAYER_PATH` ADDS to the loader's implicit-layer search (loader
|
||||
// 1.3.234+), so the box's own layer directories keep working; the distro's gamescope
|
||||
// layer is then switched off by name through its own variables, leaving exactly one
|
||||
// gamescope WSI layer live — ours.
|
||||
Self::Ours => vec![
|
||||
("VK_ADD_IMPLICIT_LAYER_PATH", OUR_WSI_LAYER_DIR),
|
||||
("PUNKTFUNK_GAMESCOPE_WSI", "1"),
|
||||
("DISABLE_GAMESCOPE_WSI", "1"),
|
||||
("ENABLE_GAMESCOPE_WSI", "0"),
|
||||
("VK_ADD_IMPLICIT_LAYER_PATH", our_wsi_layer_dir()),
|
||||
("PUNKTFUNK_GAMESCOPE_WSI", "1".to_string()),
|
||||
("DISABLE_GAMESCOPE_WSI", "1".to_string()),
|
||||
("ENABLE_GAMESCOPE_WSI", "0".to_string()),
|
||||
],
|
||||
Self::DistroKept => Vec::new(),
|
||||
Self::DistroDisabled => WSI_OFF_ENV.to_vec(),
|
||||
Self::DistroDisabled => WSI_OFF_ENV
|
||||
.iter()
|
||||
.map(|(name, value)| (*name, (*value).to_string()))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4870,12 +4884,12 @@ mod tests {
|
||||
any_output_size_is, cgroup_is_punktfunk_owned, cgroup_under_user_manager,
|
||||
classify_output_size, connected_connector_under, display_manager_unit_under, dm_plan,
|
||||
dm_survives_masked_unit, game_hz, gamescope_output_size, hdr_args, is_steam_launch,
|
||||
mask_unit, missing_flags, mode_mismatch, nested_wrapper_script, plan_bind,
|
||||
release_autologin_mask, script_hardcodes_gamescope, sentinel_advanced,
|
||||
mask_unit, missing_flags, mode_mismatch, nested_wrapper_script, our_wsi_layer_dir,
|
||||
plan_bind, release_autologin_mask, script_hardcodes_gamescope, sentinel_advanced,
|
||||
shape_dedicated_command, switch_ends_mask_window, takeover_state_is_live, unmask_unit,
|
||||
xwayland_refusal_marker, BindOff, BindPlan, BoxOutputSize, DmHelperError, SessionBind,
|
||||
TakeoverState, WsiPlan, AUTOLOGIN_MASKED, DISTRO_GAMESCOPE_PATH, OUR_WSI_LAYER_DIR,
|
||||
STOPPED_AUTOLOGIN, WSI_OFF_ENV, X11_SOCKET_DIR,
|
||||
TakeoverState, WsiPlan, AUTOLOGIN_MASKED, DISTRO_GAMESCOPE_PATH, STOPPED_AUTOLOGIN,
|
||||
WSI_OFF_ENV, X11_SOCKET_DIR,
|
||||
};
|
||||
|
||||
fn argv(s: &str) -> Vec<String> {
|
||||
@@ -5729,11 +5743,11 @@ mod tests {
|
||||
let get = |k: &str| {
|
||||
env.iter()
|
||||
.find(|(name, _)| *name == k)
|
||||
.map(|(_, v)| *v)
|
||||
.map(|(_, v)| v.clone())
|
||||
.unwrap_or_else(|| panic!("{k} missing from the Ours plan"))
|
||||
};
|
||||
|
||||
assert_eq!(get("VK_ADD_IMPLICIT_LAYER_PATH"), OUR_WSI_LAYER_DIR);
|
||||
assert_eq!(get("VK_ADD_IMPLICIT_LAYER_PATH"), our_wsi_layer_dir());
|
||||
assert_eq!(get("PUNKTFUNK_GAMESCOPE_WSI"), "1");
|
||||
// The clobber-proof one, for exactly the reason the test above states.
|
||||
assert_eq!(get("DISABLE_GAMESCOPE_WSI"), "1");
|
||||
|
||||
@@ -95,6 +95,7 @@ the full picture (and [Bazzite](/docs/bazzite) for that distro's specifics).
|
||||
| `PUNKTFUNK_GAMESCOPE_HDR` | `1` · `0` *(default on)* | Allow HDR (10-bit BT.2020 PQ) sessions on the gamescope backend. Needs the `punktfunk-gamescope` build — see [HDR on gamescope](/docs/gamescope#hdr-on-gamescope); without the build, sessions stream SDR. Set `0` to force SDR. |
|
||||
| `PUNKTFUNK_GAMESCOPE_SDR_NITS` | e.g. `400` | On an HDR gamescope session, the luminance SDR content (desktop, Steam overlay, SDR games) is mapped to inside the PQ container. Unset = 203, BT.2408 reference white, which is what our clients decode against (gamescope's own default of 400 sits nearly a stop above it). |
|
||||
| `PUNKTFUNK_GAMESCOPE_BIN` | path | Force a specific gamescope binary for the sessions the host spawns. Unset = prefer `punktfunk-gamescope` on `PATH`, then `gamescope`. |
|
||||
| `PUNKTFUNK_GAMESCOPE_WSI_LAYER_DIR` | path | Directory holding our Vulkan WSI layer's manifest — the layer that lets a game nested under gamescope get an HDR10 swapchain. Unset = `/usr/lib/punktfunk/vulkan/implicit_layer.d`, where every distro package installs it. The NixOS module sets this for you, since the layer lives inside the gamescope derivation there. If no manifest is found the host leaves the system's own layer alone and games stay SDR. |
|
||||
| `PUNKTFUNK_SESSION_WATCH` | `1` · `0` | Follow a Gaming ↔ Desktop switch **mid-stream** (rebuild the backend in place, no reconnect). **On by default** on Bazzite/SteamOS; set `0` to disable. |
|
||||
| `PUNKTFUNK_GAMESCOPE_GRAB_CURSOR` | `1` | Add `--force-grab-cursor` to a bare gamescope session the host spawns **to run an app or game** (never the empty keep-alive session), forcing relative-mouse capture so FPS mouselook works over the injected pointer. **Off by default** — relative mode breaks absolute-pointer titles and menus, so turn it on per host. |
|
||||
| `PUNKTFUNK_GAMESCOPE_SPLASH` | `1` · `0` *(default on)* | Run the built-in splash client inside each bare gamescope session the host spawns. **Leave it on**: gamescope only produces capture buffers once something paints, and a Steam launch paints nothing for its whole bootstrap — without the splash a fresh session starves and times out. `0` is a debugging escape hatch. |
|
||||
|
||||
@@ -69,6 +69,10 @@
|
||||
# from source and would make `nix flake check` an hour long.
|
||||
punktfunk-gamescope = pkgs.callPackage ./packaging/nix/gamescope.nix {
|
||||
patchDir = ./packaging/gamescope/patches;
|
||||
# Shared verbatim with build-punktfunk-gamescope.sh, which is the whole reason it is a
|
||||
# file: the FHS packages and the Nix store must rename the WSI layer identically, or
|
||||
# the host looks for a layer name that only one of them produces.
|
||||
manifestRewriter = ./packaging/gamescope/rewrite-wsi-layer-manifest.py;
|
||||
};
|
||||
};
|
||||
in
|
||||
|
||||
@@ -77,7 +77,15 @@ if [ -n "$GAMESCOPE" ]; then
|
||||
[ -x "$GS_BIN" ] || { echo "$GAMESCOPE did not provide usr/bin/punktfunk-gamescope" >&2; exit 1; }
|
||||
"$GS_BIN" --version 2>&1 | grep -q '+pfhdr' || {
|
||||
echo "$GAMESCOPE's binary has no +pfhdr marker — it is not a punktfunk HDR build" >&2; exit 1; }
|
||||
echo "folded in $("$GS_BIN" --version 2>&1 | head -1)"
|
||||
# The package carries the Vulkan WSI layer alongside the compositor and the extraction above takes
|
||||
# the whole `usr`, so this is an assertion rather than a step — but a silent one is exactly how
|
||||
# this went wrong before: an image with the compositor and no layer streams HDR while every game
|
||||
# inside it renders SDR, and nothing anywhere says why.
|
||||
for f in usr/lib/punktfunk/libVkLayer_PUNKTFUNK_gamescope_wsi.so \
|
||||
usr/lib/punktfunk/vulkan/implicit_layer.d/punktfunk_gamescope_wsi.json; do
|
||||
[ -f "$STAGE/$f" ] || { echo "$GAMESCOPE has no $f — no game HDR without it" >&2; exit 1; }
|
||||
done
|
||||
echo "folded in $("$GS_BIN" --version 2>&1 | head -1) + its WSI layer"
|
||||
fi
|
||||
|
||||
# The marker systemd-sysext requires to merge the image. ID=_any merges onto ANY host os-release
|
||||
|
||||
@@ -24,15 +24,20 @@
|
||||
#
|
||||
# Usage:
|
||||
# bash build-sysext.sh --version-id 43 --out dist/punktfunk-0.7.1-1-x86-64.raw \
|
||||
# [--gamescope path/to/punktfunk-gamescope] \
|
||||
# [--gamescope-stage path/to/gamescope-destdir] \
|
||||
# dist/punktfunk-0.7.1-1.fc43.x86_64.rpm dist/punktfunk-web-0.7.1-1.fc43.noarch.rpm
|
||||
#
|
||||
# --gamescope folds in a prebuilt HDR-capable gamescope (packaging/gamescope) as
|
||||
# --gamescope-stage folds in a prebuilt HDR-capable gamescope (packaging/gamescope) as
|
||||
# /usr/bin/punktfunk-gamescope, which is what lets the gamescope backend stream 10-bit BT.2020 PQ.
|
||||
# It is NOT built here: it is a C++ meson build with gamescope's whole dependency set, so CI builds
|
||||
# it in the same Fedora container beforehand (`bash packaging/gamescope/build-punktfunk-gamescope.sh
|
||||
# --destdir stage --prefix /usr`) and passes the resulting binary in. Omit it and the image is
|
||||
# exactly what it was — the host then stays SDR on that backend, by design.
|
||||
# --destdir stage --prefix /usr`) and passes that DESTDIR in. Omit it and the image is exactly what
|
||||
# it was — the host then stays SDR on that backend, by design.
|
||||
#
|
||||
# A directory rather than the binary, because the tree also carries the Vulkan WSI layer built beside
|
||||
# the compositor. That layer is the only route to an HDR10 swapchain for a game nested under
|
||||
# gamescope, so an image with the compositor and without it would stream HDR while every game in it
|
||||
# rendered SDR.
|
||||
#
|
||||
# The installed image MUST be named punktfunk.raw (the embedded extension-release marker is
|
||||
# extension-release.punktfunk; systemd-sysext requires marker == image name) — the feed carries
|
||||
@@ -44,7 +49,7 @@ while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--version-id) VERSION_ID="${2:?}"; shift 2 ;;
|
||||
--out) OUT="${2:?}"; shift 2 ;;
|
||||
--gamescope) GAMESCOPE="${2:?}"; shift 2 ;;
|
||||
--gamescope-stage) GAMESCOPE="${2:?}"; shift 2 ;;
|
||||
*) RPMS+=("$1"); shift ;;
|
||||
esac
|
||||
done
|
||||
@@ -87,15 +92,27 @@ if [ -d "$STAGE/etc" ]; then
|
||||
fi
|
||||
rm -rf "${STAGE:?}/var" # rpm ghosts etc. — nothing outside /usr may remain
|
||||
|
||||
# The HDR-capable gamescope, when one was built (see --gamescope in the header). Verified by its
|
||||
# The HDR-capable gamescope, when one was built (see --gamescope-stage in the header). Verified by its
|
||||
# banner marker rather than trusted by filename: an unpatched gamescope shipped under this name
|
||||
# would make the host promise HDR it cannot deliver, and the punktfunk/1 Welcome cannot take that
|
||||
# back mid-session.
|
||||
if [ -n "$GAMESCOPE" ]; then
|
||||
[ -x "$GAMESCOPE" ] || { echo "no such executable: $GAMESCOPE" >&2; exit 1; }
|
||||
"$GAMESCOPE" --version 2>&1 | grep -q '+pfhdr' || {
|
||||
echo "$GAMESCOPE has no +pfhdr marker — it is not a punktfunk HDR build" >&2; exit 1; }
|
||||
install -Dm0755 "$GAMESCOPE" "$STAGE/usr/bin/punktfunk-gamescope"
|
||||
GS_BIN="$GAMESCOPE/usr/bin/punktfunk-gamescope"
|
||||
GS_LAYER_SO="$GAMESCOPE/usr/lib/punktfunk/libVkLayer_PUNKTFUNK_gamescope_wsi.so"
|
||||
GS_LAYER_JSON="$GAMESCOPE/usr/lib/punktfunk/vulkan/implicit_layer.d/punktfunk_gamescope_wsi.json"
|
||||
[ -x "$GS_BIN" ] || { echo "no such executable: $GS_BIN" >&2; exit 1; }
|
||||
"$GS_BIN" --version 2>&1 | grep -q '+pfhdr' || {
|
||||
echo "$GS_BIN has no +pfhdr marker — it is not a punktfunk HDR build" >&2; exit 1; }
|
||||
# Fatal for the same reason the marker check is: an image carrying the compositor without its
|
||||
# layer streams HDR while every game inside it renders SDR, and says nothing about why.
|
||||
for f in "$GS_LAYER_SO" "$GS_LAYER_JSON"; do
|
||||
[ -f "$f" ] || { echo "$f missing — the gamescope stage has no WSI layer" >&2; exit 1; }
|
||||
done
|
||||
install -Dm0755 "$GS_BIN" "$STAGE/usr/bin/punktfunk-gamescope"
|
||||
install -Dm0755 "$GS_LAYER_SO" \
|
||||
"$STAGE/usr/lib/punktfunk/libVkLayer_PUNKTFUNK_gamescope_wsi.so"
|
||||
install -Dm0644 "$GS_LAYER_JSON" \
|
||||
"$STAGE/usr/lib/punktfunk/vulkan/implicit_layer.d/punktfunk_gamescope_wsi.json"
|
||||
fi
|
||||
|
||||
# Enable the plugin/script runner for every user, by baking its `[Install] WantedBy=default.target`
|
||||
|
||||
@@ -10,24 +10,38 @@
|
||||
# not Provide/Conflict with it. Only the sessions punktfunk-host starts itself resolve this binary
|
||||
# (PUNKTFUNK_GAMESCOPE_BIN > punktfunk-gamescope > gamescope).
|
||||
#
|
||||
# `--stage` is the DESTDIR build-punktfunk-gamescope.sh wrote, not a single binary: that tree carries
|
||||
# the compositor AND the Vulkan WSI layer built beside it, and a game nested under gamescope gets its
|
||||
# HDR10 swapchain from that layer or from nowhere. Taking the whole tree is what stops the next file
|
||||
# in the package needing a new flag in every packaging script.
|
||||
#
|
||||
# Usage:
|
||||
# VERSION=3.16.25.pfhdr4~ci42.gdeadbee bash packaging/debian/build-gamescope-deb.sh \
|
||||
# --binary gs-cache/punktfunk-gamescope [--arch amd64]
|
||||
# --stage gs-cache [--arch amd64]
|
||||
# Output: dist/punktfunk-gamescope_<version>_<arch>.deb
|
||||
set -euo pipefail
|
||||
|
||||
BINARY=""
|
||||
SRC_STAGE=""
|
||||
DEB_ARCH=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--binary) BINARY="${2:?--binary needs a path}"; shift 2 ;;
|
||||
--stage) SRC_STAGE="${2:?--stage needs a path}"; shift 2 ;;
|
||||
--arch) DEB_ARCH="${2:?--arch needs a value}"; shift 2 ;;
|
||||
*) echo "unknown argument: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ -n "$BINARY" ] || { echo "ERROR: --binary is required" >&2; exit 2; }
|
||||
[ -n "$SRC_STAGE" ] || { echo "ERROR: --stage is required" >&2; exit 2; }
|
||||
# The layout build-punktfunk-gamescope.sh writes under its --destdir/--prefix.
|
||||
BINARY="$SRC_STAGE/usr/bin/punktfunk-gamescope"
|
||||
LAYER_SO="$SRC_STAGE/usr/lib/punktfunk/libVkLayer_PUNKTFUNK_gamescope_wsi.so"
|
||||
LAYER_JSON="$SRC_STAGE/usr/lib/punktfunk/vulkan/implicit_layer.d/punktfunk_gamescope_wsi.json"
|
||||
[ -x "$BINARY" ] || { echo "ERROR: $BINARY is not an executable file" >&2; exit 1; }
|
||||
# Hard, not best-effort: a package carrying the compositor without its layer looks perfectly healthy
|
||||
# and then silently denies every game an HDR10 swapchain.
|
||||
for f in "$LAYER_SO" "$LAYER_JSON"; do
|
||||
[ -f "$f" ] || { echo "ERROR: $f missing from the stage — no game HDR without it" >&2; exit 1; }
|
||||
done
|
||||
|
||||
PKG="punktfunk-gamescope"
|
||||
ROOTDIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
@@ -60,6 +74,12 @@ trap 'rm -rf "$STAGE"' EXIT
|
||||
# root-only and some tooling refuses it.
|
||||
chmod 0755 "$STAGE"
|
||||
install -Dm0755 "$BINARY" "$STAGE/usr/bin/punktfunk-gamescope"
|
||||
# /usr/lib/punktfunk, not a multiarch triplet dir: the layer manifest carries that absolute path
|
||||
# baked in at build time, so the two have to agree. Nothing links the .so by soname — the Vulkan
|
||||
# loader dlopens it by exactly that path — so multiarch has no say here.
|
||||
install -Dm0755 "$LAYER_SO" "$STAGE/usr/lib/punktfunk/libVkLayer_PUNKTFUNK_gamescope_wsi.so"
|
||||
install -Dm0644 "$LAYER_JSON" \
|
||||
"$STAGE/usr/lib/punktfunk/vulkan/implicit_layer.d/punktfunk_gamescope_wsi.json"
|
||||
mkdir -p "$STAGE/DEBIAN"
|
||||
|
||||
# Shared-library dependencies straight from the binary's own ELF NEEDED entries. That is what makes
|
||||
@@ -103,7 +123,10 @@ fi
|
||||
echo " * --pipewire-composite-external-overlay: the mangoapp performance overlay is painted"
|
||||
echo " into the capture stream, so the fps/stats readout is visible remotely."
|
||||
echo " ."
|
||||
echo " Installed as /usr/bin/punktfunk-gamescope; your system gamescope is untouched."
|
||||
echo " Installed as /usr/bin/punktfunk-gamescope, with its matching Vulkan WSI layer under"
|
||||
echo " /usr/lib/punktfunk. The layer has its own name and its own enable variable, so it sits"
|
||||
echo " beside your gamescope package's rather than replacing it; your system gamescope is"
|
||||
echo " untouched."
|
||||
} > "$STAGE/DEBIAN/control"
|
||||
|
||||
mkdir -p dist
|
||||
|
||||
@@ -7,19 +7,24 @@
|
||||
# had no packaged route at all, which is how a field report ended up on a stock gamescope streaming
|
||||
# a session that told every game the display was 60 Hz.
|
||||
#
|
||||
# The binary is NOT built here; CI builds it once per Fedora major and caches it
|
||||
# Nothing is BUILT here; CI builds once per Fedora major and caches the staged tree
|
||||
# (.gitea/workflows/rpm.yml). See punktfunk-gamescope.spec's header for why repacking beats
|
||||
# rebuilding.
|
||||
#
|
||||
# `--stage` is the DESTDIR that build-punktfunk-gamescope.sh wrote, not a single binary: that tree
|
||||
# carries the compositor AND the WSI layer built beside it, and a game gets an HDR10 swapchain from
|
||||
# that layer or from nowhere. Taking the whole tree rather than a file per artifact is deliberate —
|
||||
# it is what stops the next file added to the package needing a new flag in four packaging scripts.
|
||||
#
|
||||
# Usage:
|
||||
# bash packaging/gamescope/build-gamescope-rpm.sh \
|
||||
# --binary gs-cache/punktfunk-gamescope \
|
||||
# --stage gs-cache \
|
||||
# [--version 3.16.25] [--release 1] [--outdir dist]
|
||||
#
|
||||
# Output: <outdir>/punktfunk-gamescope-<version>-<release>.<arch>.rpm
|
||||
set -euo pipefail
|
||||
|
||||
BINARY=""
|
||||
STAGE=""
|
||||
# Default the version to the upstream gamescope the pinned revision describes as, suffixed with the
|
||||
# patch-set revision — same shape as the Arch package's `pkgver`, so the two channels read alike.
|
||||
VERSION=""
|
||||
@@ -28,7 +33,7 @@ OUTDIR="dist"
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--binary) BINARY="${2:?--binary needs a path}"; shift 2 ;;
|
||||
--stage) STAGE="${2:?--stage needs a path}"; shift 2 ;;
|
||||
--version) VERSION="${2:?--version needs a value}"; shift 2 ;;
|
||||
--release) RELEASE="${2:?--release needs a value}"; shift 2 ;;
|
||||
--outdir) OUTDIR="${2:?--outdir needs a value}"; shift 2 ;;
|
||||
@@ -36,8 +41,18 @@ while [ $# -gt 0 ]; do
|
||||
esac
|
||||
done
|
||||
|
||||
[ -n "$BINARY" ] || { echo "ERROR: --binary is required" >&2; exit 2; }
|
||||
[ -n "$STAGE" ] || { echo "ERROR: --stage is required" >&2; exit 2; }
|
||||
# The layout build-punktfunk-gamescope.sh writes under its --destdir/--prefix.
|
||||
BINARY="$STAGE/usr/bin/punktfunk-gamescope"
|
||||
LAYER_SO="$STAGE/usr/lib/punktfunk/libVkLayer_PUNKTFUNK_gamescope_wsi.so"
|
||||
LAYER_JSON="$STAGE/usr/lib/punktfunk/vulkan/implicit_layer.d/punktfunk_gamescope_wsi.json"
|
||||
[ -x "$BINARY" ] || { echo "ERROR: $BINARY is not an executable file" >&2; exit 1; }
|
||||
# Hard, not best-effort. A package that carries the compositor without its layer looks completely
|
||||
# healthy and then silently denies every game an HDR10 swapchain — the failure this whole change
|
||||
# exists to end. Better to fail the packaging step than to ship that quietly again.
|
||||
for f in "$LAYER_SO" "$LAYER_JSON"; do
|
||||
[ -f "$f" ] || { echo "ERROR: $f missing from the stage — no game HDR without it" >&2; exit 1; }
|
||||
done
|
||||
|
||||
ROOTDIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
cd "$ROOTDIR"
|
||||
@@ -66,6 +81,8 @@ TOP="$(mktemp -d)"
|
||||
trap 'rm -rf "$TOP"' EXIT
|
||||
mkdir -p "$TOP"/{SOURCES,SPECS,BUILD,BUILDROOT,RPMS,SRPMS}
|
||||
install -m0755 "$BINARY" "$TOP/SOURCES/punktfunk-gamescope"
|
||||
install -m0755 "$LAYER_SO" "$TOP/SOURCES/libVkLayer_PUNKTFUNK_gamescope_wsi.so"
|
||||
install -m0644 "$LAYER_JSON" "$TOP/SOURCES/punktfunk_gamescope_wsi.json"
|
||||
|
||||
mkdir -p "$OUTDIR"
|
||||
rpmbuild \
|
||||
|
||||
@@ -180,26 +180,8 @@ LAYER_DEST_JSON="${DESTDIR}${PREFIX}/lib/punktfunk/vulkan/implicit_layer.d/punkt
|
||||
echo "==> installing ${DESTDIR}${LAYER_LIB_PATH}"
|
||||
install -Dm755 "$LAYER_SO" "${DESTDIR}${LAYER_LIB_PATH}"
|
||||
install -d "$(dirname "$LAYER_DEST_JSON")"
|
||||
python3 - "$LAYER_SRC_JSON" "$LAYER_DEST_JSON" "$LAYER_LIB_PATH" <<'PY'
|
||||
import json, sys
|
||||
|
||||
src, dst, lib = sys.argv[1:4]
|
||||
with open(src) as f:
|
||||
manifest = json.load(f)
|
||||
layer = manifest["layer"]
|
||||
# A distinct name is what lets our layer and the distro's coexist: the loader deduplicates implicit
|
||||
# layers by name, and with both called VK_LAYER_FROG_gamescope_wsi which one wins is unspecified.
|
||||
layer["name"] = "VK_LAYER_PUNKTFUNK_gamescope_wsi"
|
||||
# Absolute, so resolution never depends on where the loader happened to find the manifest.
|
||||
layer["library_path"] = lib
|
||||
# Our own gates. The distro layer's ENABLE_GAMESCOPE_WSI / DISABLE_GAMESCOPE_WSI keep working on the
|
||||
# distro layer alone, so the host can switch the two independently in one session.
|
||||
layer["enable_environment"] = {"PUNKTFUNK_GAMESCOPE_WSI": "1"}
|
||||
layer["disable_environment"] = {"PUNKTFUNK_GAMESCOPE_WSI_DISABLE": "1"}
|
||||
with open(dst, "w") as f:
|
||||
json.dump(manifest, f, indent=2)
|
||||
f.write("\n")
|
||||
PY
|
||||
python3 "$(dirname "$0")/rewrite-wsi-layer-manifest.py" \
|
||||
"$LAYER_SRC_JSON" "$LAYER_DEST_JSON" "$LAYER_LIB_PATH"
|
||||
|
||||
if [ "$SETCAP" = 1 ] && command -v setcap >/dev/null; then
|
||||
# gamescope raises its own scheduling priority; without CAP_SYS_NICE it still runs, just noisier
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
# The counterpart for Arch is packaging/gamescope/PKGBUILD, which DOES build from source, because
|
||||
# makepkg fetches sources by design and the AUR-style recipe is what an Arch user expects.
|
||||
#
|
||||
# Usage: bash packaging/gamescope/build-gamescope-rpm.sh --binary <path-to-punktfunk-gamescope>
|
||||
# Usage: bash packaging/gamescope/build-gamescope-rpm.sh --stage <destdir-the-build-script-wrote>
|
||||
Name: punktfunk-gamescope
|
||||
Version: %{pf_version}
|
||||
Release: %{pf_release}%{?dist}
|
||||
@@ -19,6 +19,12 @@ Summary: gamescope with punktfunk's PipeWire capture patches (HDR, cursor
|
||||
License: BSD-2-Clause
|
||||
URL: https://git.unom.io/unom/punktfunk
|
||||
Source0: punktfunk-gamescope
|
||||
# The Vulkan WSI layer built from the same tree at the same rev as the compositor above. A game
|
||||
# nested under gamescope gets its HDR10 swapchain from this layer and from nothing else, and a layer
|
||||
# built for a DIFFERENT gamescope kills every Vulkan client — so the two ship together or the
|
||||
# package is a trap.
|
||||
Source1: libVkLayer_PUNKTFUNK_gamescope_wsi.so
|
||||
Source2: punktfunk_gamescope_wsi.json
|
||||
|
||||
# Not `Provides: gamescope` and not `Conflicts:` either — this ships a differently-named binary and
|
||||
# is designed to coexist. A box's Game Mode session keeps running the distro's gamescope; only the
|
||||
@@ -49,7 +55,10 @@ packaging/gamescope/patches:
|
||||
* --pipewire-composite-external-overlay: paint the mangoapp performance overlay into the capture
|
||||
stream, so the fps/stats readout is visible to someone watching remotely.
|
||||
|
||||
Installed as /usr/bin/punktfunk-gamescope. Your system gamescope is untouched.
|
||||
Installed as /usr/bin/punktfunk-gamescope, with its matching Vulkan WSI layer under
|
||||
/usr/lib/punktfunk. The layer carries its own name and its own enable variable, so it sits beside
|
||||
the one your gamescope package installs rather than replacing it, and only sessions punktfunk-host
|
||||
starts switch to it. Your system gamescope is untouched.
|
||||
|
||||
%prep
|
||||
# Nothing to unpack: Source0 IS the binary.
|
||||
@@ -60,6 +69,14 @@ Installed as /usr/bin/punktfunk-gamescope. Your system gamescope is untouched.
|
||||
%install
|
||||
install -Dm0755 %{SOURCE0} %{buildroot}%{_bindir}/punktfunk-gamescope
|
||||
|
||||
# /usr/lib, spelled literally rather than %{_libdir}, which is /usr/lib64 here. The layer's manifest
|
||||
# carries an ABSOLUTE library_path baked in at build time (/usr/lib/punktfunk/...), so this path and
|
||||
# that string have to agree or the loader finds a manifest pointing at nothing. Nothing links this
|
||||
# .so by soname — the Vulkan loader dlopens it by that absolute path — so there is no multilib
|
||||
# question to answer, and a private vendor directory is where it belongs.
|
||||
install -Dm0755 %{SOURCE1} %{buildroot}/usr/lib/punktfunk/libVkLayer_PUNKTFUNK_gamescope_wsi.so
|
||||
install -Dm0644 %{SOURCE2} %{buildroot}/usr/lib/punktfunk/vulkan/implicit_layer.d/punktfunk_gamescope_wsi.json
|
||||
|
||||
%check
|
||||
# The marker is the host's entire capability probe (`gamescope_patch_level()`): a binary that lost
|
||||
# the patches would install fine and then silently stream SDR with no cursor. Refuse to package it.
|
||||
@@ -71,8 +88,22 @@ install -Dm0755 %{SOURCE0} %{buildroot}%{_bindir}/punktfunk-gamescope
|
||||
exit 1
|
||||
}
|
||||
|
||||
# The manifest's absolute library_path must name the file we actually installed. Getting this wrong
|
||||
# (%{_libdir} on a multilib box, a renamed .so) produces a package that installs cleanly and then
|
||||
# does nothing at all — the loader reads a manifest, finds no library, and moves on in silence.
|
||||
LAYER_LIB="$(grep -o '"library_path"[[:space:]]*:[[:space:]]*"[^"]*"' \
|
||||
%{buildroot}/usr/lib/punktfunk/vulkan/implicit_layer.d/punktfunk_gamescope_wsi.json \
|
||||
| sed 's/.*"\(\/[^"]*\)".*/\1/')"
|
||||
[ -f "%{buildroot}${LAYER_LIB}" ] || {
|
||||
echo "punktfunk-gamescope: the layer manifest points at ${LAYER_LIB}, which this package does" >&2
|
||||
echo " not install — games would silently get no HDR swapchain" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
%files
|
||||
%{_bindir}/punktfunk-gamescope
|
||||
/usr/lib/punktfunk/libVkLayer_PUNKTFUNK_gamescope_wsi.so
|
||||
/usr/lib/punktfunk/vulkan/implicit_layer.d/punktfunk_gamescope_wsi.json
|
||||
|
||||
%changelog
|
||||
# Generated per build; see the git history for the patch set's own changes.
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Rewrite gamescope's generated Vulkan layer manifest so OUR copy of the layer can be installed
|
||||
beside the distro's instead of colliding with it.
|
||||
|
||||
A game nested under gamescope gets its HDR10 swapchain from the FROG WSI layer and from nothing
|
||||
else, and that layer speaks `gamescope_swapchain` to the compositor: a layer built for a DIFFERENT
|
||||
gamescope makes the compositor reject the client's swapchain_feedback, and every Vulkan client dies
|
||||
on a black screen with sound and input and no error. So a compositor we ship needs the layer we
|
||||
built beside it — which means two gamescope WSI layers on one box.
|
||||
|
||||
Three fields make that safe, and the loader is why:
|
||||
|
||||
* `name` — the Vulkan loader deduplicates implicit layers by name, and with both called
|
||||
VK_LAYER_FROG_gamescope_wsi which one wins is unspecified. A distinct name is what lets both sit
|
||||
installed at once.
|
||||
* `library_path` — made absolute, so resolution never depends on where the loader found the
|
||||
manifest.
|
||||
* `enable_environment` / `disable_environment` — our own gates, so the host can switch ours ON and
|
||||
the distro's OFF in the same session. Sharing ENABLE_GAMESCOPE_WSI would make that impossible.
|
||||
|
||||
Everything else is passed through untouched, `functions` above all: it names the layer's entry
|
||||
points, and a manifest with the wrong ones is a layer that silently never loads.
|
||||
|
||||
Used by build-punktfunk-gamescope.sh (FHS packaging) and packaging/nix/gamescope.nix (the Nix store),
|
||||
which is the point of it being a file rather than a heredoc — the two must not drift.
|
||||
|
||||
Usage: rewrite-wsi-layer-manifest.py <src.json> <dst.json> <installed-library-path>
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
|
||||
LAYER_NAME = "VK_LAYER_PUNKTFUNK_gamescope_wsi"
|
||||
ENABLE_VAR = "PUNKTFUNK_GAMESCOPE_WSI"
|
||||
DISABLE_VAR = "PUNKTFUNK_GAMESCOPE_WSI_DISABLE"
|
||||
|
||||
|
||||
def main(argv):
|
||||
if len(argv) != 4:
|
||||
print(__doc__, file=sys.stderr)
|
||||
return 2
|
||||
src, dst, lib = argv[1:4]
|
||||
|
||||
with open(src) as f:
|
||||
manifest = json.load(f)
|
||||
|
||||
layer = manifest.get("layer")
|
||||
if not isinstance(layer, dict):
|
||||
print(f"{src}: no 'layer' object — not a Vulkan layer manifest", file=sys.stderr)
|
||||
return 1
|
||||
# A manifest that never named the entry points would produce a layer that loads and does
|
||||
# nothing, which is indistinguishable on a running box from "this GPU has no HDR".
|
||||
if not layer.get("functions") and not layer.get("library_path"):
|
||||
print(f"{src}: neither 'functions' nor 'library_path' — refusing to rewrite", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
layer["name"] = LAYER_NAME
|
||||
layer["library_path"] = lib
|
||||
layer["enable_environment"] = {ENABLE_VAR: "1"}
|
||||
layer["disable_environment"] = {DISABLE_VAR: "1"}
|
||||
|
||||
with open(dst, "w") as f:
|
||||
json.dump(manifest, f, indent=2)
|
||||
f.write("\n")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
||||
@@ -31,7 +31,9 @@
|
||||
{
|
||||
lib,
|
||||
gamescope,
|
||||
python3,
|
||||
patchDir,
|
||||
manifestRewriter,
|
||||
}:
|
||||
let
|
||||
# As of nixos-unstable (checked 2026-07-28) `gamescope` IS the buildable derivation — pname
|
||||
@@ -81,14 +83,39 @@ unwrapped.overrideAttrs (old: {
|
||||
"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.
|
||||
# Ship the compositor, renamed, AND the WSI layer built beside it. Everything else nixpkgs
|
||||
# installs (gamescopectl, gamescopereaper, gamescopestream, .desktop files) belongs to the real
|
||||
# gamescope package — duplicating it here would put two of each on PATH.
|
||||
#
|
||||
# The layer is not dressing: a game nested under this compositor gets its HDR10 swapchain from it
|
||||
# or from nowhere, and a layer built for a DIFFERENT gamescope makes the compositor reject the
|
||||
# client's swapchain_feedback and kills every Vulkan client. So it travels with the binary it was
|
||||
# built against. It is renamed and re-homed under $out/lib/punktfunk, with its own enable
|
||||
# variable, so it sits beside the system gamescope's layer rather than shadowing it — the Vulkan
|
||||
# loader deduplicates implicit layers by name, so two of the same name would be a coin toss.
|
||||
#
|
||||
# Staged through $TMPDIR because the prune below removes $out/lib and $out/share wholesale.
|
||||
postInstall = (old.postInstall or "") + ''
|
||||
layerSo=$(find $out -type f -name 'libVkLayer_*gamescope_wsi*.so' | head -1)
|
||||
layerJson=$(find $out -type f -name '*gamescope_wsi*.json' | head -1)
|
||||
if [ -z "$layerSo" ] || [ -z "$layerJson" ]; then
|
||||
echo "punktfunk-gamescope: this nixpkgs' gamescope built no WSI layer, so no game under the" >&2
|
||||
echo " compositor could ever obtain an HDR10 swapchain" >&2
|
||||
exit 1
|
||||
fi
|
||||
cp "$layerSo" "$TMPDIR/pf-layer.so"
|
||||
${python3}/bin/python3 ${manifestRewriter} \
|
||||
"$layerJson" "$TMPDIR/pf-layer.json" \
|
||||
"$out/lib/punktfunk/libVkLayer_PUNKTFUNK_gamescope_wsi.so"
|
||||
|
||||
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
|
||||
|
||||
install -Dm0755 "$TMPDIR/pf-layer.so" \
|
||||
"$out/lib/punktfunk/libVkLayer_PUNKTFUNK_gamescope_wsi.so"
|
||||
install -Dm0644 "$TMPDIR/pf-layer.json" \
|
||||
"$out/lib/punktfunk/vulkan/implicit_layer.d/punktfunk_gamescope_wsi.json"
|
||||
'';
|
||||
|
||||
# `gamescope --version` exits non-zero on some builds; the grep is the real assertion.
|
||||
@@ -97,6 +124,13 @@ unwrapped.overrideAttrs (old: {
|
||||
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; }
|
||||
# The manifest must name a library this derivation actually installed. A manifest pointing at a
|
||||
# path that does not exist is the worst shape of this bug: the loader reads it, finds nothing,
|
||||
# and carries on silently, so the box looks healthy and every game renders SDR.
|
||||
lib=$(sed -n 's/.*"library_path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \
|
||||
$out/lib/punktfunk/vulkan/implicit_layer.d/punktfunk_gamescope_wsi.json)
|
||||
[ -f "$lib" ] \
|
||||
|| { echo "punktfunk-gamescope: the layer manifest points at $lib, which is not installed"; exit 1; }
|
||||
runHook postInstallCheck
|
||||
'';
|
||||
|
||||
|
||||
@@ -507,6 +507,16 @@ in
|
||||
# can still override this (or set it to `off` to force the in-process encoder) — the same
|
||||
# "an operator's own override still wins" posture as PUNKTFUNK_GAMESCOPE_BIN above.
|
||||
environment.PUNKTFUNK_ENCODE_WORKER = "${config.security.wrapperDir}/punktfunk-encode-worker";
|
||||
# Where our Vulkan WSI layer's manifest lives. The host defaults to the FHS path every
|
||||
# distro package uses, which no NixOS box has — here the layer travels inside the gamescope
|
||||
# derivation, so point at it. Without this a game nested under the compositor gets no HDR10
|
||||
# swapchain at all: that layer is the only route to one, and the host falls back to
|
||||
# disabling the system layer, which is HDR-less by construction.
|
||||
#
|
||||
# Same override posture as PUNKTFUNK_GAMESCOPE_BIN: `Environment=` renders before
|
||||
# `EnvironmentFile=`, so an operator's `settings` still wins.
|
||||
environment.PUNKTFUNK_GAMESCOPE_WSI_LAYER_DIR = mkIf cfg.host.gamescopeHdr
|
||||
"${cfg.host.gamescopePackage}/lib/punktfunk/vulkan/implicit_layer.d";
|
||||
serviceConfig = {
|
||||
# The store path DIRECTLY — not a capability wrapper. /proc/<pid>/exe then resolves to the
|
||||
# very path packages.nix substituted into io.unom.Punktfunk.Host.desktop's Exec=, which is
|
||||
|
||||
Reference in New Issue
Block a user