Returning from a stream left every menu laid out against the wrong safe area: content shoved right, the profile row sliding under the status bar, the tab labels crowding the gesture pill. Dumped on the reporter's phone, the window's real insets were bars=[0,162,0,72] cutout=[0,162,0,0] while the layout was using the landscape immersive set — cutout left=162 (Material3 lays out against systemBars.union(displayCutout)), bars all zero. No rotation and no IME animation could shake it loose. Compose attaches its OnApplyWindowInsets and WindowInsetsAnimation callbacks when the first composable reads an inset and removes them when the last reader goes away (WindowInsetsHolder.increment / decrementAccessors). StreamScreen reads no insets at all, so a stream drops that count to zero for its whole duration. Survivable on its own — but a session that ends while the app is BACKGROUNDED is the common case (leaving the app ends the session), and then the entire window restore runs on a stopped activity. The corrected insets arrive while Compose has no listener attached; when the menus recompose, incrementAccessors re-attaches and asks for a fresh pass, but a stopped window produces no dispatch and on resume nothing has changed any more, so none ever comes. Compose keeps serving the landscape, bars-hidden values for the rest of the process. Hold one inset reader at the root for the activity's whole life, so the listeners survive the stream and every dispatch lands. It subscribes to no inset VALUE, only the holder object, so it costs one DisposableEffect and no recomposition. Verified on the reporter's device by replaying the real teardown sequence (forced landscape + immersive, composition swapped to a screen that reads no insets, torn down while stopped) and reading the layout back through uiautomator: 3 of 4 runs wrong without this, 4 of 4 correct with it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
244 lines
13 KiB
YAML
244 lines
13 KiB
YAML
# Build the punktfunk-host / punktfunk-client / punktfunk-web / punktfunk-scripting pacman packages from
|
|
# packaging/arch/PKGBUILD and publish them to Gitea's Arch package registry, so Arch boxes
|
|
# get new builds via `pacman -Syu`. Counterpart to deb.yml (apt) and rpm.yml (dnf/rpm-ostree).
|
|
# Arch is rolling, so the packages build against whatever the archlinux:base-devel container
|
|
# resolves today — the same sonames an up-to-date Arch box runs.
|
|
#
|
|
# Registry (public, unom org) — box setup (once), see packaging/arch/README.md. The registry
|
|
# SIGNS the DB + packages, so the box imports the registry key first (pacman-key --add +
|
|
# --lsign-key), then no SigLevel line is needed (pacman's default Required verifies):
|
|
# [punktfunk] # or [punktfunk-canary] for main-push builds
|
|
# Server = https://git.unom.io/api/packages/unom/arch/$repo/$arch
|
|
#
|
|
# REGISTRY_TOKEN: repo Actions secret, a PAT with write:package scope (shared with docker.yml).
|
|
# NOTE: this token + the registry-held private key are the trust root — a token holder can
|
|
# publish a validly-signed package (the signature attests "via the registry", not "built by CI").
|
|
name: arch
|
|
# One pending run per workflow+ref: a newer push supersedes the queued/running one and cancels
|
|
# it (a canary only needs the latest commit; each release tag is its own ref so tag runs never
|
|
# cancel each other). Keeps a busy push cadence from piling ~10 queued runs per commit onto the
|
|
# runner fleet. Gitea honors this for push triggers (PR triggers: see gitea#35933).
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
# Scope canary builds to what this artifact is built FROM — a docs-only or
|
|
# web-only push should not light up the whole fleet. Applies to branch pushes;
|
|
# tag runs are matched by `tags:` (proven by flatpak/windows-msix releases).
|
|
paths:
|
|
- 'crates/**'
|
|
- 'clients/linux/**'
|
|
- 'clients/session/**'
|
|
- 'clients/shared/**'
|
|
- 'clients/cli/**'
|
|
- 'web/**'
|
|
- 'sdk/**'
|
|
- 'packaging/arch/**'
|
|
- 'packaging/gamescope/**'
|
|
- 'Cargo.toml'
|
|
- 'Cargo.lock'
|
|
- 'rust-toolchain.toml'
|
|
- 'scripts/ci/**'
|
|
- '.gitea/workflows/arch.yml'
|
|
# Single project version: a `vX.Y.Z` tag is THE release. main publishes to the
|
|
# `punktfunk-canary` pacman repo as X.Y.Z-0.<run#> (sorts below the eventual X.Y.Z-1),
|
|
# tags to `punktfunk` — separate repos, so neither channel can shadow the other.
|
|
tags: ['v*']
|
|
workflow_dispatch:
|
|
|
|
env:
|
|
REGISTRY: git.unom.io
|
|
OWNER: unom
|
|
# Shared compile cache: sccache -> RustFS S3 (storage.unom.io). NOTE: makepkg runs
|
|
# behind `sudo -u builder env ...`, which strips ambient env — the makepkg step
|
|
# re-exports these explicitly.
|
|
RUSTC_WRAPPER: sccache
|
|
SCCACHE_BUCKET: unom-ci-sccache
|
|
SCCACHE_ENDPOINT: https://storage.unom.io
|
|
SCCACHE_REGION: home-central
|
|
AWS_ACCESS_KEY_ID: ${{ secrets.SCCACHE_ACCESS_KEY_ID }}
|
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.SCCACHE_SECRET_ACCESS_KEY }}
|
|
CARGO_INCREMENTAL: "0"
|
|
|
|
jobs:
|
|
build-publish:
|
|
runs-on: ubuntu-24.04
|
|
container:
|
|
# Everything the two pacman steps below used to download (~1 GB/run) is baked in,
|
|
# plus bun, sccache and node (ci/arch-ci.Dockerfile). The steps stay as --needed
|
|
# no-op guards for the one push where :latest lags an image-content change.
|
|
image: 192.168.1.58:5010/punktfunk-arch-ci:latest
|
|
timeout-minutes: 90
|
|
env:
|
|
CARGO_HOME: /usr/local/cargo
|
|
steps:
|
|
# git + nodejs must exist before actions/checkout — base-devel ships neither, and
|
|
# act_runner runs the action's JS with the CONTAINER's node, it does not inject one.
|
|
- name: Build + runtime-dev deps (no-op guard — baked into arch-ci)
|
|
# No -Syu: the image's snapshot IS the build environment (see the Dockerfile's
|
|
# rolling-release note); with everything installed this resolves locally and
|
|
# does nothing. It only matters on the push that adds a dep before the image
|
|
# rebuild lands — same bootstrap note as ci.yml's GTK4 step.
|
|
run: |
|
|
pacman -S --noconfirm --needed \
|
|
git nodejs rust clang cmake ninja nasm pkgconf python vulkan-headers \
|
|
gtk4 libadwaita sdl3 ffmpeg pipewire wayland libxkbcommon opus libei \
|
|
mesa libglvnd unzip libarchive || echo "::warning::pacman guard failed (stale image db?) — proceeding with baked packages"
|
|
command -v bun >/dev/null || {
|
|
curl -fsSL https://bun.sh/install | bash
|
|
install -m0755 "$HOME/.bun/bin/bun" /usr/local/bin/bun
|
|
}
|
|
bun --version
|
|
|
|
- uses: actions/checkout@v4
|
|
|
|
# Cache cargo's git dir too, not just the registry: the workspace includes
|
|
# clients/windows, whose windows-reactor/windows deps are git-pinned — cargo must CLONE
|
|
# them (windows-rs is huge) merely to resolve the workspace, even though nothing Windows
|
|
# is ever compiled here. Cached, that cost is paid once per runner.
|
|
- uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
/usr/local/cargo/registry
|
|
/usr/local/cargo/git
|
|
key: cargo-home-arch-${{ hashFiles('Cargo.lock') }}
|
|
restore-keys: cargo-home-arch-
|
|
|
|
- name: Version + channel
|
|
# vX.Y.Z tag -> X.Y.Z-1 in the `punktfunk` repo; main push -> <next-minor>-0.<run#> in
|
|
# `punktfunk-canary` (pkgrel accepts only digits+dots — the run number carries the
|
|
# monotonic ordering; the commit sha is stamped into the binary via the workflow log).
|
|
#
|
|
# The run number is ZERO-PADDED to a fixed width, and that padding is load-bearing.
|
|
# pacman's own vercmp compares numeric segments numerically and gets this right either
|
|
# way, but Gitea's Arch registry picks the version it advertises in `punktfunk-canary.db`
|
|
# by STRING order. Unpadded, the run counter crossing a power of ten inverts that order
|
|
# ("0.9907" > "0.10095" because '9' > '1'), so the db pins itself to the last build
|
|
# before the rollover and every later canary becomes invisible to `pacman -Syu` — the
|
|
# packages publish fine, the index just never names them. That is exactly what happened
|
|
# on 2026-07-29 when run #10000 landed; it cost an evening and needed a manual purge of
|
|
# every 4-digit `0.22.0-0.9xxx` version to unstick. Padding keeps string order and
|
|
# numeric order in agreement, so the two can never disagree again.
|
|
#
|
|
# Keep the leading `0.` — it is what sorts a canary BELOW the eventual `X.Y.Z-1` stable
|
|
# release. (A pkgrel is digits+dots only, so `0.` is the only prefix available; raising
|
|
# it to `1.` would sort canaries ABOVE the release and is not an option.)
|
|
run: |
|
|
eval "$(bash scripts/ci/pf-version.sh)" # -> PF_BASE (one minor ahead of latest stable)
|
|
case "$GITHUB_REF" in
|
|
refs/tags/v*) V="${GITHUB_REF_NAME#v}"; R="1"; REPO=punktfunk ;;
|
|
*) V="$PF_BASE"; R="0.$(printf '%08d' "$GITHUB_RUN_NUMBER")"; REPO=punktfunk-canary ;;
|
|
esac
|
|
echo "PF_PKGVER=$V" >> "$GITHUB_ENV"
|
|
echo "PF_PKGREL=$R" >> "$GITHUB_ENV"
|
|
echo "REPO=$REPO" >> "$GITHUB_ENV"
|
|
echo "pacman $V-$R -> repo '$REPO'"
|
|
|
|
- name: Build packages (makepkg)
|
|
run: |
|
|
git config --global --add safe.directory "$PWD"
|
|
# libcuda link stub — same trick as packaging/rpm/build-rpm.sh: the zerocopy FFI
|
|
# links -lcuda but the builder has no GPU; synthesize every cu* symbol the source
|
|
# references so a newly-added call can't silently break the link.
|
|
CU_SYMS="$(grep -rhoE '\bcu[A-Z][A-Za-z0-9_]*' crates/punktfunk-host/src/ | sort -u || true)"
|
|
if [ -n "$CU_SYMS" ] && [ ! -e /usr/lib/libcuda.so ]; then
|
|
STUB_C="$(mktemp --suffix=.c)"
|
|
for s in $CU_SYMS; do printf 'int %s(void){return 0;}\n' "$s" >> "$STUB_C"; done
|
|
gcc -shared -fPIC -Wl,-soname,libcuda.so.1 -o /usr/lib/libcuda.so.1 "$STUB_C"
|
|
ln -sf libcuda.so.1 /usr/lib/libcuda.so
|
|
rm -f "$STUB_C"; ldconfig
|
|
echo "== libcuda stub: $(printf '%s\n' "$CU_SYMS" | wc -l) symbols =="
|
|
fi
|
|
# makepkg refuses to run as root; deps are already installed above (-d skips the
|
|
# RPM-level check that can't see the script-installed bun anyway).
|
|
useradd -m builder
|
|
mkdir -p "$CARGO_HOME" # actions/cache doesn't create it on a cache miss
|
|
chown -R builder: "$PWD" "$CARGO_HOME"
|
|
sudo -u builder git config --global --add safe.directory "$PWD"
|
|
mkdir -p dist && chown builder: dist
|
|
cd packaging/arch
|
|
# sudo env_reset strips the ambient env, so the sccache wiring must cross the
|
|
# boundary explicitly (same values as the workflow env block).
|
|
sudo -u builder env PF_SRCDIR="$GITHUB_WORKSPACE" PF_WITH_WEB=1 PF_WITH_SCRIPTING=1 \
|
|
PF_PKGVER="$PF_PKGVER" PF_PKGREL="$PF_PKGREL" \
|
|
CARGO_HOME="$CARGO_HOME" PKGDEST="$GITHUB_WORKSPACE/dist" \
|
|
RUSTC_WRAPPER="$RUSTC_WRAPPER" CARGO_INCREMENTAL="$CARGO_INCREMENTAL" \
|
|
SCCACHE_BUCKET="$SCCACHE_BUCKET" SCCACHE_ENDPOINT="$SCCACHE_ENDPOINT" \
|
|
SCCACHE_REGION="$SCCACHE_REGION" \
|
|
AWS_ACCESS_KEY_ID="$AWS_ACCESS_KEY_ID" AWS_SECRET_ACCESS_KEY="$AWS_SECRET_ACCESS_KEY" \
|
|
makepkg -f -d --holdver
|
|
ls -lh "$GITHUB_WORKSPACE/dist"
|
|
|
|
# The optional HDR gamescope companion (packaging/gamescope) — a separate pkgbase with a
|
|
# completely different dependency set, published into the same repo so `pacman -S
|
|
# punktfunk-gamescope` is all an Arch/SteamOS box needs for 10-bit BT.2020 PQ.
|
|
#
|
|
# CACHED on `packaging/gamescope/**`: it depends on nothing else in this repo, so a normal
|
|
# push restores the built package instead of spending ~10 minutes on someone else's C++ tree.
|
|
# Arch is rolling, so the cache is invalidated by our own patch changes only — a stale binary
|
|
# against newer system libs is the same risk the distro's own package carries between rebuilds.
|
|
- uses: actions/cache@v4
|
|
id: gamescope
|
|
with:
|
|
path: dist-gamescope
|
|
key: punktfunk-gamescope-arch-${{ hashFiles('packaging/gamescope/**') }}
|
|
|
|
- name: Build punktfunk-gamescope (makepkg)
|
|
if: steps.gamescope.outputs.cache-hit != 'true'
|
|
# Best-effort: punktfunk-host works without it (SDR on the gamescope backend), and a
|
|
# failure building gamescope must not cost the packages this workflow exists to publish.
|
|
run: |
|
|
set -x
|
|
# Baked into arch-ci — a no-op guard, like the dep step above.
|
|
pacman -S --noconfirm --needed \
|
|
glslang libcap libdrm libinput libx11 libxcomposite libxdamage libxext \
|
|
libxkbcommon libxmu libxrender libxres libxtst libxxf86vm libavif libdecor \
|
|
hwdata luajit pipewire seatd sdl2-compat vulkan-icd-loader wayland \
|
|
xcb-util-errors xcb-util-wm xorg-xwayland \
|
|
meson cmake glm wayland-protocols benchmark libxcursor || true
|
|
mkdir -p dist-gamescope && chown builder: dist-gamescope
|
|
chown -R builder: packaging/gamescope
|
|
if sudo -u builder env PKGDEST="$GITHUB_WORKSPACE/dist-gamescope" \
|
|
bash -c 'cd packaging/gamescope && makepkg -f -d --holdver'; then
|
|
ls -lh dist-gamescope
|
|
else
|
|
echo "::warning::punktfunk-gamescope failed to build — Arch boxes stay SDR on the gamescope backend this run"
|
|
rm -rf dist-gamescope # never cache a failed build (an empty path is not saved)
|
|
fi
|
|
|
|
- name: Publish to the Gitea Arch registry
|
|
env:
|
|
TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
|
run: |
|
|
# The gamescope companion rides the same loop (same repo, same channel).
|
|
cp -f dist-gamescope/*.pkg.tar.zst dist/ 2>/dev/null || true
|
|
for pkg in dist/*.pkg.tar.zst; do
|
|
echo "uploading $pkg"
|
|
NAME=$(bsdtar -xOf "$pkg" .PKGINFO | sed -n 's/^pkgname = //p')
|
|
VER=$(bsdtar -xOf "$pkg" .PKGINFO | sed -n 's/^pkgver = //p')
|
|
ARCH=$(bsdtar -xOf "$pkg" .PKGINFO | sed -n 's/^arch = //p')
|
|
# A re-tagged release re-fires this workflow and the registry 409s on duplicate
|
|
# package versions — delete any prior copy first (404 on the first publish is fine).
|
|
curl -fsS -o /dev/null --user "enricobuehler:$TOKEN" -X DELETE \
|
|
"https://$REGISTRY/api/packages/$OWNER/arch/$REPO/$NAME/$VER/$ARCH" || true
|
|
curl -fsS --user "enricobuehler:$TOKEN" --upload-file "$pkg" \
|
|
"https://$REGISTRY/api/packages/$OWNER/arch/$REPO"
|
|
done
|
|
echo "published to $OWNER/arch/$REPO"
|
|
|
|
# On a real release, also attach the packages to the unified Gitea Release.
|
|
- name: Attach packages to the Gitea release (stable tags only)
|
|
if: startsWith(gitea.ref, 'refs/tags/v')
|
|
env:
|
|
GITEA_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
|
run: |
|
|
. scripts/ci/gitea-release.sh
|
|
RID=$(ensure_release "$GITHUB_REF_NAME" "$GITHUB_REF_NAME" auto)
|
|
for pkg in dist/*.pkg.tar.zst; do
|
|
upsert_asset "$RID" "$pkg"
|
|
done
|