Merge remote-tracking branch 'origin/main' into worktree-release-0250
This commit is contained in:
@@ -173,6 +173,31 @@ jobs:
|
||||
makepkg -f -d --holdver
|
||||
ls -lh "$GITHUB_WORKSPACE/dist"
|
||||
|
||||
# The host must ship a VERSIONED libav soname dep, and nothing else in this pipeline proves
|
||||
# it. packaging/arch/PKGBUILD lists bare `libavcodec.so` etc. and relies on makepkg rewriting
|
||||
# each into `libavcodec.so=<soname>-<arch>` from the built binary's DT_NEEDED; if that
|
||||
# rewrite ever stops happening — Arch dropping the soname `provides`, someone "tidying" the
|
||||
# entries out of `depends`, a makepkg change — the dep silently degrades to an unversioned
|
||||
# name that ANY ffmpeg satisfies. That is precisely the 2026-08-08 state in which `pacman
|
||||
# -Syu` walked every Arch/CachyOS install across the FFmpeg 8 -> 9 soname bump and left the
|
||||
# host unable to start (exit 127 before main(), restart loop). The failure is invisible in a
|
||||
# green build and only shows up as a bricked box weeks later, so assert it here.
|
||||
- name: Assert the host pins the FFmpeg soname
|
||||
run: |
|
||||
PKG="$(ls "$GITHUB_WORKSPACE"/dist/punktfunk-host-*.pkg.tar.zst | head -1)"
|
||||
DEPS="$(bsdtar -xOf "$PKG" .PKGINFO | sed -n 's/^depend = //p')"
|
||||
echo "$DEPS" | sed 's/^/ depend = /'
|
||||
for lib in libavcodec libavutil; do
|
||||
echo "$DEPS" | grep -qE "^$lib\.so=[0-9]+-[0-9]+$" || {
|
||||
echo "::error::punktfunk-host declares no VERSIONED $lib.so dependency."
|
||||
echo "::error::makepkg did not expand the bare soname from DT_NEEDED, so pacman can"
|
||||
echo "::error::upgrade FFmpeg across a soname break and brick the install."
|
||||
echo "::error::See the depends comment in packaging/arch/PKGBUILD."
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
echo "OK: $(echo "$DEPS" | grep -E '^libav|^libsw' | tr '\n' ' ')"
|
||||
|
||||
# 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.
|
||||
|
||||
@@ -66,6 +66,13 @@ jobs:
|
||||
test -f node_modules/@punktfunk/host/package.json
|
||||
test -f node_modules/@punktfunk/host/dist/index.d.ts
|
||||
|
||||
# The kit had no biome config and no lint step, while every plugin repo that consumes it does
|
||||
# — so its source drifted (unused imports, formatting) with nothing to catch it. Now gated
|
||||
# here, on the same config and pinned biome version the plugins use.
|
||||
- name: Lint & format
|
||||
working-directory: plugin-kit
|
||||
run: bun run check
|
||||
|
||||
- name: Typecheck
|
||||
working-directory: plugin-kit
|
||||
run: bun run typecheck
|
||||
|
||||
Generated
+8
-8
@@ -647,9 +647,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.65"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96"
|
||||
checksum = "9066c49992464636f92905fa096ec58baaa4d57ec19a5c096c68d3e25ef3d136"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@@ -1290,9 +1290,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ffmpeg-next"
|
||||
version = "8.1.0"
|
||||
version = "9.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7c4bd5ab1ac61f29c634df1175d350ded29cf74c3c6d4f7030431a5ae3c7d5d"
|
||||
checksum = "6380599799e175191eb7ffe82c97f36a2a90a36cbc54c738a903e5287d7f516a"
|
||||
dependencies = [
|
||||
"bitflags 2.13.0",
|
||||
"ffmpeg-sys-next",
|
||||
@@ -1301,9 +1301,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ffmpeg-sys-next"
|
||||
version = "8.1.0"
|
||||
version = "9.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a314bc0e022a33a99567ed4bd2576bd58ffd8fcff7891c29194cfecc26a62547"
|
||||
checksum = "9b939bf79dd5949412a4b81cfe21a07f48ea21b47fcbb5f57816c8c2de5ae30b"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
@@ -1341,9 +1341,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de"
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
|
||||
@@ -83,7 +83,7 @@ MANIFEST (crate version — SPDX license — source)
|
||||
cast 0.3.0 — MIT OR Apache-2.0 — https://github.com/japaric/cast.rs
|
||||
cbc 0.1.2 — MIT OR Apache-2.0 — https://github.com/RustCrypto/block-modes
|
||||
cbindgen 0.29.4 — MPL-2.0 — https://github.com/mozilla/cbindgen
|
||||
cc 1.2.65 — MIT OR Apache-2.0 — https://github.com/rust-lang/cc-rs
|
||||
cc 1.4.1 — MIT OR Apache-2.0 — https://github.com/rust-lang/cc-rs
|
||||
cesu8 1.1.0 — Apache-2.0/MIT — https://github.com/emk/cesu8-rs
|
||||
cexpr 0.6.0 — Apache-2.0/MIT — https://github.com/jethrogb/rust-cexpr
|
||||
cfg-expr 0.20.8 — MIT OR Apache-2.0 — https://github.com/EmbarkStudios/cfg-expr
|
||||
@@ -148,12 +148,12 @@ MANIFEST (crate version — SPDX license — source)
|
||||
fastbloom 0.14.1 — MIT OR Apache-2.0 — https://github.com/tomtomwombat/fastbloom/
|
||||
fastrand 2.4.1 — Apache-2.0 OR MIT — https://github.com/smol-rs/fastrand
|
||||
fdeflate 0.3.7 — MIT OR Apache-2.0 — https://github.com/image-rs/fdeflate
|
||||
ffmpeg-next 8.1.0 — WTFPL — https://github.com/zmwangx/rust-ffmpeg
|
||||
ffmpeg-sys-next 8.1.0 — WTFPL — https://github.com/zmwangx/rust-ffmpeg-sys
|
||||
ffmpeg-next 9.0.0 — WTFPL — https://github.com/zmwangx/rust-ffmpeg
|
||||
ffmpeg-sys-next 9.0.0 — WTFPL — https://github.com/zmwangx/rust-ffmpeg-sys
|
||||
fiat-crypto 0.2.9 — MIT OR Apache-2.0 OR BSD-1-Clause — https://github.com/mit-plv/fiat-crypto
|
||||
field-offset 0.3.6 — MIT OR Apache-2.0 — https://github.com/Diggsey/rust-field-offset
|
||||
filetime 0.2.29 — MIT/Apache-2.0 — https://github.com/alexcrichton/filetime
|
||||
find-msvc-tools 0.1.9 — MIT OR Apache-2.0 — https://github.com/rust-lang/cc-rs
|
||||
find-msvc-tools 0.1.10 — MIT OR Apache-2.0 — https://github.com/rust-lang/cc-rs
|
||||
fixedbitset 0.5.7 — MIT OR Apache-2.0 — https://github.com/petgraph/fixedbitset
|
||||
flate2 1.1.9 — MIT OR Apache-2.0 — https://github.com/rust-lang/flate2-rs
|
||||
flume 0.12.0 — Apache-2.0/MIT — https://github.com/zesterer/flume
|
||||
@@ -628,7 +628,7 @@ Crates whose package did not embed a license file (SPDX + source only)
|
||||
atomig-macro 0.4.0 — MIT/Apache-2.0 — https://github.com/LukasKalbertodt/atomig/
|
||||
cookie-factory 0.3.3 — MIT — https://github.com/rust-bakery/cookie-factory
|
||||
defmt-parser 1.0.0 — MIT OR Apache-2.0 — https://github.com/knurling-rs/defmt
|
||||
ffmpeg-sys-next 8.1.0 — WTFPL — https://github.com/zmwangx/rust-ffmpeg-sys
|
||||
ffmpeg-sys-next 9.0.0 — WTFPL — https://github.com/zmwangx/rust-ffmpeg-sys
|
||||
jni-sys-macros 0.4.1 — MIT OR Apache-2.0 — https://github.com/jni-rs/jni-sys
|
||||
ndk 0.9.0 — MIT OR Apache-2.0 — https://github.com/rust-mobile/ndk
|
||||
ndk-sys 0.6.0+11769913 — MIT OR Apache-2.0 — https://github.com/rust-mobile/ndk
|
||||
@@ -2266,7 +2266,7 @@ SOFTWARE.
|
||||
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
The following license (LICENSE-APACHE) applies to: asn1-rs 0.6.2, asn1-rs-derive 0.5.1, assert_matches 1.5.0, async-channel 2.5.0, async-executor 1.14.0, async-io 2.6.0, async-lock 3.4.2, async-process 2.5.0, async-recursion 1.1.1, async-signal 0.2.14, async-task 4.7.1, atomic-waker 1.1.2, autocfg 1.5.1, base64 0.22.1, bitflags 1.3.2, bitflags 2.13.0, blocking 1.6.2, bumpalo 3.20.3, cast 0.3.0, cc 1.2.65, cexpr 0.6.0, cfg-if 1.0.4, cmake 0.1.58, concurrent-queue 2.5.0, core-foundation 0.10.1, core-foundation-sys 0.8.7, criterion 0.5.1, criterion-plot 0.5.0, crossbeam-deque 0.8.6, crossbeam-epoch 0.9.20, crossbeam-utils 0.8.21, curve25519-dalek-derive 0.1.1, der-parser 9.0.0, displaydoc 0.2.6, either 1.16.0, equivalent 1.0.2, errno 0.3.14, event-listener 5.4.1, event-listener-strategy 0.5.4, fastrand 2.4.1, filetime 0.2.29, find-msvc-tools 0.1.9, fixedbitset 0.5.7, flate2 1.1.9, fnv 1.0.7, form_urlencoded 1.2.2, fs-err 3.3.0, futures-lite 2.6.1, gethostname 1.1.0, gif 0.14.2, glob 0.3.3, hashbrown 0.16.1, hashbrown 0.17.1, heck 0.5.0, hermit-abi 0.5.2, httparse 1.10.1, idna 1.1.0, idna_adapter 1.2.2, indexmap 2.14.0, itertools 0.10.5, itertools 0.13.0, jni 0.21.1, jobserver 0.1.34, js-sys 0.3.103, khronos-egl 6.0.0, lazy_static 1.5.0, linux-raw-sys 0.12.1, lock_api 0.4.14, log 0.4.33, mime 0.3.17, nasm-rs 0.3.2, num-bigint 0.4.6, num-bigint-dig 0.8.6, num-derive 0.4.2, num-integer 0.1.46, num-iter 0.1.45, num-traits 0.2.19, num_cpus 1.17.0, oid-registry 0.7.1, once_cell 1.21.4, openssl-probe 0.2.1, opus 0.3.1, ordered-stream 0.2.0, parking 2.2.1, parking_lot 0.12.5, parking_lot_core 0.9.12, percent-encoding 2.3.2, piper 0.2.5, pkg-config 0.3.33, png 0.18.1, polling 3.11.0, proptest 1.11.0, rayon 1.12.0, rayon-core 1.13.0, regex 1.12.4, regex-automata 0.4.14, regex-syntax 0.8.11, roxmltree 0.21.1, rsa 0.9.10, rustc_version 0.4.1, rusticata-macros 4.1.0, rustix 1.1.4, rustls 0.23.41, rustls-native-certs 0.8.4, rusty-fork 0.3.1, scopeguard 1.2.0, security-framework 3.7.0, security-framework-sys 2.17.0, signal-hook-registry 1.4.8, smallvec 1.15.2, socket2 0.6.4, stable_deref_trait 1.2.1, system-deps 7.0.8, tar 0.4.46, tempfile 3.27.0, thread_local 1.1.9, tinytemplate 1.2.1, unicode-segmentation 1.13.3, unicode-width 0.2.2, url 2.5.8, uuid 1.23.4, vcpkg 0.2.15, version_check 0.9.5, wait-timeout 0.2.1, wasi 0.11.1+wasi-snapshot-preview1, wasip2 1.0.4+wasi-0.2.12, wasm-bindgen 0.2.126, wasm-bindgen-macro 0.2.126, wasm-bindgen-macro-support 0.2.126, wasm-bindgen-shared 0.2.126, weezl 0.1.12, wit-bindgen 0.57.1, x509-parser 0.16.0, xattr 1.6.1
|
||||
The following license (LICENSE-APACHE) applies to: asn1-rs 0.6.2, asn1-rs-derive 0.5.1, assert_matches 1.5.0, async-channel 2.5.0, async-executor 1.14.0, async-io 2.6.0, async-lock 3.4.2, async-process 2.5.0, async-recursion 1.1.1, async-signal 0.2.14, async-task 4.7.1, atomic-waker 1.1.2, autocfg 1.5.1, base64 0.22.1, bitflags 1.3.2, bitflags 2.13.0, blocking 1.6.2, bumpalo 3.20.3, cast 0.3.0, cc 1.4.1, cexpr 0.6.0, cfg-if 1.0.4, cmake 0.1.58, concurrent-queue 2.5.0, core-foundation 0.10.1, core-foundation-sys 0.8.7, criterion 0.5.1, criterion-plot 0.5.0, crossbeam-deque 0.8.6, crossbeam-epoch 0.9.20, crossbeam-utils 0.8.21, curve25519-dalek-derive 0.1.1, der-parser 9.0.0, displaydoc 0.2.6, either 1.16.0, equivalent 1.0.2, errno 0.3.14, event-listener 5.4.1, event-listener-strategy 0.5.4, fastrand 2.4.1, filetime 0.2.29, find-msvc-tools 0.1.10, fixedbitset 0.5.7, flate2 1.1.9, fnv 1.0.7, form_urlencoded 1.2.2, fs-err 3.3.0, futures-lite 2.6.1, gethostname 1.1.0, gif 0.14.2, glob 0.3.3, hashbrown 0.16.1, hashbrown 0.17.1, heck 0.5.0, hermit-abi 0.5.2, httparse 1.10.1, idna 1.1.0, idna_adapter 1.2.2, indexmap 2.14.0, itertools 0.10.5, itertools 0.13.0, jni 0.21.1, jobserver 0.1.34, js-sys 0.3.103, khronos-egl 6.0.0, lazy_static 1.5.0, linux-raw-sys 0.12.1, lock_api 0.4.14, log 0.4.33, mime 0.3.17, nasm-rs 0.3.2, num-bigint 0.4.6, num-bigint-dig 0.8.6, num-derive 0.4.2, num-integer 0.1.46, num-iter 0.1.45, num-traits 0.2.19, num_cpus 1.17.0, oid-registry 0.7.1, once_cell 1.21.4, openssl-probe 0.2.1, opus 0.3.1, ordered-stream 0.2.0, parking 2.2.1, parking_lot 0.12.5, parking_lot_core 0.9.12, percent-encoding 2.3.2, piper 0.2.5, pkg-config 0.3.33, png 0.18.1, polling 3.11.0, proptest 1.11.0, rayon 1.12.0, rayon-core 1.13.0, regex 1.12.4, regex-automata 0.4.14, regex-syntax 0.8.11, roxmltree 0.21.1, rsa 0.9.10, rustc_version 0.4.1, rusticata-macros 4.1.0, rustix 1.1.4, rustls 0.23.41, rustls-native-certs 0.8.4, rusty-fork 0.3.1, scopeguard 1.2.0, security-framework 3.7.0, security-framework-sys 2.17.0, signal-hook-registry 1.4.8, smallvec 1.15.2, socket2 0.6.4, stable_deref_trait 1.2.1, system-deps 7.0.8, tar 0.4.46, tempfile 3.27.0, thread_local 1.1.9, tinytemplate 1.2.1, unicode-segmentation 1.13.3, unicode-width 0.2.2, url 2.5.8, uuid 1.23.4, vcpkg 0.2.15, version_check 0.9.5, wait-timeout 0.2.1, wasi 0.11.1+wasi-snapshot-preview1, wasip2 1.0.4+wasi-0.2.12, wasm-bindgen 0.2.126, wasm-bindgen-macro 0.2.126, wasm-bindgen-macro-support 0.2.126, wasm-bindgen-shared 0.2.126, weezl 0.1.12, wit-bindgen 0.57.1, x509-parser 0.16.0, xattr 1.6.1
|
||||
----------------------------------------------------------------------------
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
@@ -4011,7 +4011,7 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
The following license (LICENSE-MIT) applies to: cc 1.2.65, cfg-if 1.0.4, cmake 0.1.58, filetime 0.2.29, find-msvc-tools 0.1.9, jobserver 0.1.34, js-sys 0.3.103, openssl-probe 0.2.1, pkg-config 0.3.33, socket2 0.6.4, wait-timeout 0.2.1, wasm-bindgen 0.2.126, wasm-bindgen-macro 0.2.126, wasm-bindgen-macro-support 0.2.126, wasm-bindgen-shared 0.2.126
|
||||
The following license (LICENSE-MIT) applies to: cc 1.4.1, cfg-if 1.0.4, cmake 0.1.58, filetime 0.2.29, find-msvc-tools 0.1.10, jobserver 0.1.34, js-sys 0.3.103, openssl-probe 0.2.1, pkg-config 0.3.33, socket2 0.6.4, wait-timeout 0.2.1, wasm-bindgen 0.2.126, wasm-bindgen-macro 0.2.126, wasm-bindgen-macro-support 0.2.126, wasm-bindgen-shared 0.2.126
|
||||
----------------------------------------------------------------------------
|
||||
Copyright (c) 2014 Alex Crichton
|
||||
|
||||
@@ -6183,7 +6183,7 @@ DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
The following license (LICENSE) applies to: ffmpeg-next 8.1.0
|
||||
The following license (LICENSE) applies to: ffmpeg-next 9.0.0
|
||||
----------------------------------------------------------------------------
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
+10
-1
@@ -9,7 +9,16 @@
|
||||
# from the last image rebuild instead of a fresh -Syu per run. That is the same staleness
|
||||
# the gamescope cache already embraces ("a stale binary against newer system libs is the
|
||||
# same risk the distro's own package carries between rebuilds"), and any ci/ edit — or
|
||||
# bumping the date in this line (refreshed: 2026-07-29) — re-keys and re-snapshots it.
|
||||
# bumping the date in this line (refreshed: 2026-08-08) — re-keys and re-snapshots it.
|
||||
#
|
||||
# ⚠ That staleness has a sharp edge, and 2026-08-08 is why the date above moved: this snapshot is
|
||||
# what decides which FFmpeg the HOST links, and arch.yml deliberately runs no -Syu, so the builder
|
||||
# stayed frozen on ffmpeg 8 (libavcodec 62) even after Arch shipped 2:9.0-5 (libavcodec 63) to
|
||||
# every user. A canary built from the old snapshot therefore CANNOT satisfy the soname dep that
|
||||
# packaging/arch/PKGBUILD now derives from the link (libavcodec.so=62-64 against a box that has
|
||||
# 63-64), so it would simply refuse to install rather than start. Re-keying this image is the step
|
||||
# that makes the ffmpeg-9 bump actually reach the package — a Cargo.toml bump alone does nothing
|
||||
# here. Whenever Arch moves to an FFmpeg major, bump the date in the same commit.
|
||||
FROM docker.io/library/archlinux:base-devel
|
||||
|
||||
# One transaction: the main build/runtime deps (first list) + the gamescope companion's
|
||||
|
||||
@@ -45,6 +45,14 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
# Sourced from the official FFmpeg GitHub mirror by release tag, NOT ffmpeg.org: the CI build network
|
||||
# can't reach ffmpeg.org (curl times out) but reaches github.com fine. The `nX.Y` tag pins the version
|
||||
# (n8.0 -> libavcodec 62); bump it to move FFmpeg. Immutable-tag clone, so no separate checksum needed.
|
||||
#
|
||||
# STAYING ON 8.0 THROUGH THE 2026-08-08 FFmpeg-9 BUMP IS DELIBERATE. `ffmpeg-next` moved to 9, but a
|
||||
# crate major is a CEILING (ffmpeg-sys-next 9 spans libavcodec 56..63), so an 8.0 tree still compiles
|
||||
# — and this .deb is the one package with NO exposure to the soname break that motivated the bump: it
|
||||
# BUNDLES these libs into /usr/lib/punktfunk-host behind an rpath and strips the libav* sonames from
|
||||
# its Depends, so nothing the user's apt does can move them underneath it. Bumping this tag would
|
||||
# re-qualify the encode stack for every Ubuntu user and buy none of them anything, so it is its own
|
||||
# change — and it drags NVHDR_TAG and the soname assertion below along with it.
|
||||
ARG FFMPEG_TAG=n8.0
|
||||
# nv-codec-headers must MATCH the FFmpeg version: its `master` is NVENC SDK 13, which renamed
|
||||
# NV_ENC_CLOCK_TIMESTAMP_SET.countingType -> countingTypeLSB and won't compile against FFmpeg 8.0's
|
||||
|
||||
@@ -13,7 +13,9 @@ ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
# toolchain + bindgen; nodejs runs the JS actions (checkout/cache); unzip is for the bun installer
|
||||
build-essential clang libclang-dev pkg-config cmake git curl ca-certificates nodejs unzip \
|
||||
# ffmpeg-next 8 (system FFmpeg 8 / libavcodec 62 on 26.04)
|
||||
# ffmpeg-next 9, built against whatever libav* 26.04 ships (FFmpeg 8 / libavcodec 62 today).
|
||||
# The crate major is a CEILING — ffmpeg-sys-next 9 spans libavcodec 56..63 — so this image does
|
||||
# not need to move in lockstep with Arch's FFmpeg 9; it just links what the distro has.
|
||||
libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavfilter-dev \
|
||||
libavdevice-dev \
|
||||
# capture / audio / display stacks (+xkbcommon for the wlr input backend)
|
||||
|
||||
@@ -49,7 +49,7 @@ MANIFEST (crate version — SPDX license — source)
|
||||
bytes 1.12.0 — MIT — https://github.com/tokio-rs/bytes
|
||||
cast 0.3.0 — MIT OR Apache-2.0 — https://github.com/japaric/cast.rs
|
||||
cbindgen 0.29.4 — MPL-2.0 — https://github.com/mozilla/cbindgen
|
||||
cc 1.2.65 — MIT OR Apache-2.0 — https://github.com/rust-lang/cc-rs
|
||||
cc 1.4.1 — MIT OR Apache-2.0 — https://github.com/rust-lang/cc-rs
|
||||
cesu8 1.1.0 — Apache-2.0/MIT — https://github.com/emk/cesu8-rs
|
||||
cfg-if 1.0.4 — MIT OR Apache-2.0 — https://github.com/rust-lang/cfg-if
|
||||
cfg_aliases 0.2.1 — MIT — https://github.com/katharostech/cfg_aliases
|
||||
@@ -88,7 +88,7 @@ MANIFEST (crate version — SPDX license — source)
|
||||
fastbloom 0.14.1 — MIT OR Apache-2.0 — https://github.com/tomtomwombat/fastbloom/
|
||||
fastrand 2.4.1 — Apache-2.0 OR MIT — https://github.com/smol-rs/fastrand
|
||||
fiat-crypto 0.2.9 — MIT OR Apache-2.0 OR BSD-1-Clause — https://github.com/mit-plv/fiat-crypto
|
||||
find-msvc-tools 0.1.9 — MIT OR Apache-2.0 — https://github.com/rust-lang/cc-rs
|
||||
find-msvc-tools 0.1.10 — MIT OR Apache-2.0 — https://github.com/rust-lang/cc-rs
|
||||
fixedbitset 0.5.7 — MIT OR Apache-2.0 — https://github.com/petgraph/fixedbitset
|
||||
flume 0.12.0 — Apache-2.0/MIT — https://github.com/zesterer/flume
|
||||
fnv 1.0.7 — Apache-2.0 / MIT — https://github.com/servo/rust-fnv
|
||||
@@ -1390,7 +1390,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
The following license (LICENSE-APACHE) applies to: autocfg 1.5.1, base64 0.22.1, bitflags 2.13.0, bumpalo 3.20.3, cast 0.3.0, cc 1.2.65, cfg-if 1.0.4, cmake 0.1.58, core-foundation 0.10.1, core-foundation-sys 0.8.7, criterion 0.5.1, criterion-plot 0.5.0, crossbeam-deque 0.8.6, crossbeam-epoch 0.9.20, crossbeam-utils 0.8.21, curve25519-dalek-derive 0.1.1, either 1.16.0, equivalent 1.0.2, errno 0.3.14, fastrand 2.4.1, find-msvc-tools 0.1.9, fixedbitset 0.5.7, fnv 1.0.7, hashbrown 0.17.1, heck 0.5.0, hermit-abi 0.5.2, indexmap 2.14.0, itertools 0.10.5, jni 0.21.1, jobserver 0.1.34, js-sys 0.3.103, linux-raw-sys 0.12.1, lock_api 0.4.14, log 0.4.33, num-traits 0.2.19, once_cell 1.21.4, openssl-probe 0.2.1, opus 0.3.1, parking_lot 0.12.5, parking_lot_core 0.9.12, pkg-config 0.3.33, proptest 1.11.0, rayon 1.12.0, rayon-core 1.13.0, regex 1.12.4, regex-automata 0.4.14, regex-syntax 0.8.11, rustc_version 0.4.1, rustix 1.1.4, rustls 0.23.41, rustls-native-certs 0.8.4, rusty-fork 0.3.1, scopeguard 1.2.0, security-framework 3.7.0, security-framework-sys 2.17.0, signal-hook-registry 1.4.8, smallvec 1.15.2, socket2 0.6.4, tempfile 3.27.0, tinytemplate 1.2.1, version_check 0.9.5, wait-timeout 0.2.1, wasi 0.11.1+wasi-snapshot-preview1, wasip2 1.0.4+wasi-0.2.12, wasm-bindgen 0.2.126, wasm-bindgen-macro 0.2.126, wasm-bindgen-macro-support 0.2.126, wasm-bindgen-shared 0.2.126, wit-bindgen 0.57.1
|
||||
The following license (LICENSE-APACHE) applies to: autocfg 1.5.1, base64 0.22.1, bitflags 2.13.0, bumpalo 3.20.3, cast 0.3.0, cc 1.4.1, cfg-if 1.0.4, cmake 0.1.58, core-foundation 0.10.1, core-foundation-sys 0.8.7, criterion 0.5.1, criterion-plot 0.5.0, crossbeam-deque 0.8.6, crossbeam-epoch 0.9.20, crossbeam-utils 0.8.21, curve25519-dalek-derive 0.1.1, either 1.16.0, equivalent 1.0.2, errno 0.3.14, fastrand 2.4.1, find-msvc-tools 0.1.10, fixedbitset 0.5.7, fnv 1.0.7, hashbrown 0.17.1, heck 0.5.0, hermit-abi 0.5.2, indexmap 2.14.0, itertools 0.10.5, jni 0.21.1, jobserver 0.1.34, js-sys 0.3.103, linux-raw-sys 0.12.1, lock_api 0.4.14, log 0.4.33, num-traits 0.2.19, once_cell 1.21.4, openssl-probe 0.2.1, opus 0.3.1, parking_lot 0.12.5, parking_lot_core 0.9.12, pkg-config 0.3.33, proptest 1.11.0, rayon 1.12.0, rayon-core 1.13.0, regex 1.12.4, regex-automata 0.4.14, regex-syntax 0.8.11, rustc_version 0.4.1, rustix 1.1.4, rustls 0.23.41, rustls-native-certs 0.8.4, rusty-fork 0.3.1, scopeguard 1.2.0, security-framework 3.7.0, security-framework-sys 2.17.0, signal-hook-registry 1.4.8, smallvec 1.15.2, socket2 0.6.4, tempfile 3.27.0, tinytemplate 1.2.1, version_check 0.9.5, wait-timeout 0.2.1, wasi 0.11.1+wasi-snapshot-preview1, wasip2 1.0.4+wasi-0.2.12, wasm-bindgen 0.2.126, wasm-bindgen-macro 0.2.126, wasm-bindgen-macro-support 0.2.126, wasm-bindgen-shared 0.2.126, wit-bindgen 0.57.1
|
||||
----------------------------------------------------------------------------
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
@@ -2435,7 +2435,7 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
The following license (LICENSE-MIT) applies to: cc 1.2.65, cfg-if 1.0.4, cmake 0.1.58, find-msvc-tools 0.1.9, jobserver 0.1.34, js-sys 0.3.103, openssl-probe 0.2.1, pkg-config 0.3.33, socket2 0.6.4, wait-timeout 0.2.1, wasm-bindgen 0.2.126, wasm-bindgen-macro 0.2.126, wasm-bindgen-macro-support 0.2.126, wasm-bindgen-shared 0.2.126
|
||||
The following license (LICENSE-MIT) applies to: cc 1.4.1, cfg-if 1.0.4, cmake 0.1.58, find-msvc-tools 0.1.10, jobserver 0.1.34, js-sys 0.3.103, openssl-probe 0.2.1, pkg-config 0.3.33, socket2 0.6.4, wait-timeout 0.2.1, wasm-bindgen 0.2.126, wasm-bindgen-macro 0.2.126, wasm-bindgen-macro-support 0.2.126, wasm-bindgen-shared 0.2.126
|
||||
----------------------------------------------------------------------------
|
||||
Copyright (c) 2014 Alex Crichton
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ MANIFEST (crate version — SPDX license — source)
|
||||
bytes 1.12.0 — MIT — https://github.com/tokio-rs/bytes
|
||||
cast 0.3.0 — MIT OR Apache-2.0 — https://github.com/japaric/cast.rs
|
||||
cbindgen 0.29.4 — MPL-2.0 — https://github.com/mozilla/cbindgen
|
||||
cc 1.2.65 — MIT OR Apache-2.0 — https://github.com/rust-lang/cc-rs
|
||||
cc 1.4.1 — MIT OR Apache-2.0 — https://github.com/rust-lang/cc-rs
|
||||
cesu8 1.1.0 — Apache-2.0/MIT — https://github.com/emk/cesu8-rs
|
||||
cfg-if 1.0.4 — MIT OR Apache-2.0 — https://github.com/rust-lang/cfg-if
|
||||
cfg_aliases 0.2.1 — MIT — https://github.com/katharostech/cfg_aliases
|
||||
@@ -85,7 +85,7 @@ MANIFEST (crate version — SPDX license — source)
|
||||
fastbloom 0.14.1 — MIT OR Apache-2.0 — https://github.com/tomtomwombat/fastbloom/
|
||||
fastrand 2.4.1 — Apache-2.0 OR MIT — https://github.com/smol-rs/fastrand
|
||||
fiat-crypto 0.2.9 — MIT OR Apache-2.0 OR BSD-1-Clause — https://github.com/mit-plv/fiat-crypto
|
||||
find-msvc-tools 0.1.9 — MIT OR Apache-2.0 — https://github.com/rust-lang/cc-rs
|
||||
find-msvc-tools 0.1.10 — MIT OR Apache-2.0 — https://github.com/rust-lang/cc-rs
|
||||
fixedbitset 0.5.7 — MIT OR Apache-2.0 — https://github.com/petgraph/fixedbitset
|
||||
fnv 1.0.7 — Apache-2.0 / MIT — https://github.com/servo/rust-fnv
|
||||
foldhash 0.2.0 — Zlib — https://github.com/orlp/foldhash
|
||||
@@ -908,7 +908,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
The following license (LICENSE-APACHE) applies to: autocfg 1.5.1, base64 0.22.1, bitflags 2.13.0, bumpalo 3.20.3, cast 0.3.0, cc 1.2.65, cfg-if 1.0.4, cmake 0.1.58, core-foundation 0.10.1, core-foundation-sys 0.8.7, criterion 0.5.1, criterion-plot 0.5.0, crossbeam-deque 0.8.6, crossbeam-epoch 0.9.20, crossbeam-utils 0.8.21, curve25519-dalek-derive 0.1.1, either 1.16.0, equivalent 1.0.2, errno 0.3.14, fastrand 2.4.1, find-msvc-tools 0.1.9, fixedbitset 0.5.7, fnv 1.0.7, hashbrown 0.17.1, heck 0.5.0, hermit-abi 0.5.2, indexmap 2.14.0, itertools 0.10.5, jni 0.21.1, jobserver 0.1.34, js-sys 0.3.103, linux-raw-sys 0.12.1, lock_api 0.4.14, log 0.4.33, num-traits 0.2.19, once_cell 1.21.4, openssl-probe 0.2.1, opus 0.3.1, parking_lot 0.12.5, parking_lot_core 0.9.12, pkg-config 0.3.33, proptest 1.11.0, rayon 1.12.0, rayon-core 1.13.0, regex 1.12.4, regex-automata 0.4.14, regex-syntax 0.8.11, rustc_version 0.4.1, rustix 1.1.4, rustls 0.23.41, rustls-native-certs 0.8.4, rusty-fork 0.3.1, scopeguard 1.2.0, security-framework 3.7.0, security-framework-sys 2.17.0, signal-hook-registry 1.4.8, smallvec 1.15.2, socket2 0.6.4, tempfile 3.27.0, tinytemplate 1.2.1, version_check 0.9.5, wait-timeout 0.2.1, wasi 0.11.1+wasi-snapshot-preview1, wasip2 1.0.4+wasi-0.2.12, wasm-bindgen 0.2.126, wasm-bindgen-macro 0.2.126, wasm-bindgen-macro-support 0.2.126, wasm-bindgen-shared 0.2.126, wit-bindgen 0.57.1
|
||||
The following license (LICENSE-APACHE) applies to: autocfg 1.5.1, base64 0.22.1, bitflags 2.13.0, bumpalo 3.20.3, cast 0.3.0, cc 1.4.1, cfg-if 1.0.4, cmake 0.1.58, core-foundation 0.10.1, core-foundation-sys 0.8.7, criterion 0.5.1, criterion-plot 0.5.0, crossbeam-deque 0.8.6, crossbeam-epoch 0.9.20, crossbeam-utils 0.8.21, curve25519-dalek-derive 0.1.1, either 1.16.0, equivalent 1.0.2, errno 0.3.14, fastrand 2.4.1, find-msvc-tools 0.1.10, fixedbitset 0.5.7, fnv 1.0.7, hashbrown 0.17.1, heck 0.5.0, hermit-abi 0.5.2, indexmap 2.14.0, itertools 0.10.5, jni 0.21.1, jobserver 0.1.34, js-sys 0.3.103, linux-raw-sys 0.12.1, lock_api 0.4.14, log 0.4.33, num-traits 0.2.19, once_cell 1.21.4, openssl-probe 0.2.1, opus 0.3.1, parking_lot 0.12.5, parking_lot_core 0.9.12, pkg-config 0.3.33, proptest 1.11.0, rayon 1.12.0, rayon-core 1.13.0, regex 1.12.4, regex-automata 0.4.14, regex-syntax 0.8.11, rustc_version 0.4.1, rustix 1.1.4, rustls 0.23.41, rustls-native-certs 0.8.4, rusty-fork 0.3.1, scopeguard 1.2.0, security-framework 3.7.0, security-framework-sys 2.17.0, signal-hook-registry 1.4.8, smallvec 1.15.2, socket2 0.6.4, tempfile 3.27.0, tinytemplate 1.2.1, version_check 0.9.5, wait-timeout 0.2.1, wasi 0.11.1+wasi-snapshot-preview1, wasip2 1.0.4+wasi-0.2.12, wasm-bindgen 0.2.126, wasm-bindgen-macro 0.2.126, wasm-bindgen-macro-support 0.2.126, wasm-bindgen-shared 0.2.126, wit-bindgen 0.57.1
|
||||
----------------------------------------------------------------------------
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
@@ -1953,7 +1953,7 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
The following license (LICENSE-MIT) applies to: cc 1.2.65, cfg-if 1.0.4, cmake 0.1.58, find-msvc-tools 0.1.9, jobserver 0.1.34, js-sys 0.3.103, openssl-probe 0.2.1, pkg-config 0.3.33, socket2 0.6.4, wait-timeout 0.2.1, wasm-bindgen 0.2.126, wasm-bindgen-macro 0.2.126, wasm-bindgen-macro-support 0.2.126, wasm-bindgen-shared 0.2.126
|
||||
The following license (LICENSE-MIT) applies to: cc 1.4.1, cfg-if 1.0.4, cmake 0.1.58, find-msvc-tools 0.1.10, jobserver 0.1.34, js-sys 0.3.103, openssl-probe 0.2.1, pkg-config 0.3.33, socket2 0.6.4, wait-timeout 0.2.1, wasm-bindgen 0.2.126, wasm-bindgen-macro 0.2.126, wasm-bindgen-macro-support 0.2.126, wasm-bindgen-shared 0.2.126
|
||||
----------------------------------------------------------------------------
|
||||
Copyright (c) 2014 Alex Crichton
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
THIRD-PARTY SOFTWARE NOTICES
|
||||
============================================================================
|
||||
|
||||
punktfunk (https://git.unom.io/unom/punktfunk) is licensed under MIT OR Apache-2.0.
|
||||
Punktfunk (https://git.unom.io/unom/punktfunk) is licensed under MIT OR Apache-2.0.
|
||||
The binaries it ships statically/dynamically link the third-party Rust crates listed
|
||||
below. Each is distributed under its own permissive license; the full license texts
|
||||
follow the manifest. This file is generated by scripts/gen-third-party-notices.py
|
||||
@@ -62,7 +62,7 @@ MANIFEST (crate version — SPDX license — source)
|
||||
cairo-sys-rs 0.22.0 — MIT — https://github.com/gtk-rs/gtk-rs-core
|
||||
cast 0.3.0 — MIT OR Apache-2.0 — https://github.com/japaric/cast.rs
|
||||
cbindgen 0.29.4 — MPL-2.0 — https://github.com/mozilla/cbindgen
|
||||
cc 1.2.65 — MIT OR Apache-2.0 — https://github.com/rust-lang/cc-rs
|
||||
cc 1.4.1 — MIT OR Apache-2.0 — https://github.com/rust-lang/cc-rs
|
||||
cesu8 1.1.0 — Apache-2.0/MIT — https://github.com/emk/cesu8-rs
|
||||
cexpr 0.6.0 — Apache-2.0/MIT — https://github.com/jethrogb/rust-cexpr
|
||||
cfg-expr 0.20.8 — MIT OR Apache-2.0 — https://github.com/EmbarkStudios/cfg-expr
|
||||
@@ -117,7 +117,7 @@ MANIFEST (crate version — SPDX license — source)
|
||||
fiat-crypto 0.2.9 — MIT OR Apache-2.0 OR BSD-1-Clause — https://github.com/mit-plv/fiat-crypto
|
||||
field-offset 0.3.6 — MIT OR Apache-2.0 — https://github.com/Diggsey/rust-field-offset
|
||||
filetime 0.2.29 — MIT/Apache-2.0 — https://github.com/alexcrichton/filetime
|
||||
find-msvc-tools 0.1.9 — MIT OR Apache-2.0 — https://github.com/rust-lang/cc-rs
|
||||
find-msvc-tools 0.1.10 — MIT OR Apache-2.0 — https://github.com/rust-lang/cc-rs
|
||||
fixedbitset 0.5.7 — MIT OR Apache-2.0 — https://github.com/petgraph/fixedbitset
|
||||
flate2 1.1.9 — MIT OR Apache-2.0 — https://github.com/rust-lang/flate2-rs
|
||||
flume 0.12.0 — Apache-2.0/MIT — https://github.com/zesterer/flume
|
||||
@@ -1625,7 +1625,7 @@ DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
The following license (LICENSE-APACHE) applies to: assert_matches 1.5.0, async-channel 2.5.0, autocfg 1.5.1, base64 0.22.1, bitflags 1.3.2, bitflags 2.13.0, bumpalo 3.20.3, cast 0.3.0, cc 1.2.65, cexpr 0.6.0, cfg-if 1.0.4, cmake 0.1.58, concurrent-queue 2.5.0, core-foundation 0.10.1, core-foundation-sys 0.8.7, criterion 0.5.1, criterion-plot 0.5.0, crossbeam-deque 0.8.6, crossbeam-epoch 0.9.20, crossbeam-utils 0.8.21, curve25519-dalek-derive 0.1.1, displaydoc 0.2.6, either 1.16.0, equivalent 1.0.2, errno 0.3.14, event-listener 5.4.1, event-listener-strategy 0.5.4, fastrand 2.4.1, filetime 0.2.29, find-msvc-tools 0.1.9, fixedbitset 0.5.7, flate2 1.1.9, fnv 1.0.7, form_urlencoded 1.2.2, glob 0.3.3, hashbrown 0.17.1, heck 0.5.0, hermit-abi 0.5.2, idna 1.1.0, idna_adapter 1.2.2, indexmap 2.14.0, itertools 0.10.5, itertools 0.13.0, jni 0.21.1, jobserver 0.1.34, js-sys 0.3.103, lazy_static 1.5.0, linux-raw-sys 0.12.1, lock_api 0.4.14, log 0.4.33, nasm-rs 0.3.2, num-integer 0.1.46, num-traits 0.2.19, once_cell 1.21.4, openssl-probe 0.2.1, opus 0.3.1, parking 2.2.1, parking_lot 0.12.5, parking_lot_core 0.9.12, percent-encoding 2.3.2, pkg-config 0.3.33, proptest 1.11.0, rayon 1.12.0, rayon-core 1.13.0, regex 1.12.4, regex-automata 0.4.14, regex-syntax 0.8.11, rustc_version 0.4.1, rustix 1.1.4, rustls 0.23.41, rustls-native-certs 0.8.4, rusty-fork 0.3.1, scopeguard 1.2.0, security-framework 3.7.0, security-framework-sys 2.17.0, signal-hook-registry 1.4.8, smallvec 1.15.2, socket2 0.6.4, stable_deref_trait 1.2.1, system-deps 7.0.8, tar 0.4.46, tempfile 3.27.0, thread_local 1.1.9, tinytemplate 1.2.1, unicode-segmentation 1.13.3, unicode-width 0.2.2, url 2.5.8, vcpkg 0.2.15, version_check 0.9.5, wait-timeout 0.2.1, wasi 0.11.1+wasi-snapshot-preview1, wasip2 1.0.4+wasi-0.2.12, wasm-bindgen 0.2.126, wasm-bindgen-macro 0.2.126, wasm-bindgen-macro-support 0.2.126, wasm-bindgen-shared 0.2.126, wit-bindgen 0.57.1, xattr 1.6.1
|
||||
The following license (LICENSE-APACHE) applies to: assert_matches 1.5.0, async-channel 2.5.0, autocfg 1.5.1, base64 0.22.1, bitflags 1.3.2, bitflags 2.13.0, bumpalo 3.20.3, cast 0.3.0, cc 1.4.1, cexpr 0.6.0, cfg-if 1.0.4, cmake 0.1.58, concurrent-queue 2.5.0, core-foundation 0.10.1, core-foundation-sys 0.8.7, criterion 0.5.1, criterion-plot 0.5.0, crossbeam-deque 0.8.6, crossbeam-epoch 0.9.20, crossbeam-utils 0.8.21, curve25519-dalek-derive 0.1.1, displaydoc 0.2.6, either 1.16.0, equivalent 1.0.2, errno 0.3.14, event-listener 5.4.1, event-listener-strategy 0.5.4, fastrand 2.4.1, filetime 0.2.29, find-msvc-tools 0.1.10, fixedbitset 0.5.7, flate2 1.1.9, fnv 1.0.7, form_urlencoded 1.2.2, glob 0.3.3, hashbrown 0.17.1, heck 0.5.0, hermit-abi 0.5.2, idna 1.1.0, idna_adapter 1.2.2, indexmap 2.14.0, itertools 0.10.5, itertools 0.13.0, jni 0.21.1, jobserver 0.1.34, js-sys 0.3.103, lazy_static 1.5.0, linux-raw-sys 0.12.1, lock_api 0.4.14, log 0.4.33, nasm-rs 0.3.2, num-integer 0.1.46, num-traits 0.2.19, once_cell 1.21.4, openssl-probe 0.2.1, opus 0.3.1, parking 2.2.1, parking_lot 0.12.5, parking_lot_core 0.9.12, percent-encoding 2.3.2, pkg-config 0.3.33, proptest 1.11.0, rayon 1.12.0, rayon-core 1.13.0, regex 1.12.4, regex-automata 0.4.14, regex-syntax 0.8.11, rustc_version 0.4.1, rustix 1.1.4, rustls 0.23.41, rustls-native-certs 0.8.4, rusty-fork 0.3.1, scopeguard 1.2.0, security-framework 3.7.0, security-framework-sys 2.17.0, signal-hook-registry 1.4.8, smallvec 1.15.2, socket2 0.6.4, stable_deref_trait 1.2.1, system-deps 7.0.8, tar 0.4.46, tempfile 3.27.0, thread_local 1.1.9, tinytemplate 1.2.1, unicode-segmentation 1.13.3, unicode-width 0.2.2, url 2.5.8, vcpkg 0.2.15, version_check 0.9.5, wait-timeout 0.2.1, wasi 0.11.1+wasi-snapshot-preview1, wasip2 1.0.4+wasi-0.2.12, wasm-bindgen 0.2.126, wasm-bindgen-macro 0.2.126, wasm-bindgen-macro-support 0.2.126, wasm-bindgen-shared 0.2.126, wit-bindgen 0.57.1, xattr 1.6.1
|
||||
----------------------------------------------------------------------------
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
@@ -2921,7 +2921,7 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
The following license (LICENSE-MIT) applies to: cc 1.2.65, cfg-if 1.0.4, cmake 0.1.58, filetime 0.2.29, find-msvc-tools 0.1.9, jobserver 0.1.34, js-sys 0.3.103, openssl-probe 0.2.1, pkg-config 0.3.33, socket2 0.6.4, wait-timeout 0.2.1, wasm-bindgen 0.2.126, wasm-bindgen-macro 0.2.126, wasm-bindgen-macro-support 0.2.126, wasm-bindgen-shared 0.2.126
|
||||
The following license (LICENSE-MIT) applies to: cc 1.4.1, cfg-if 1.0.4, cmake 0.1.58, filetime 0.2.29, find-msvc-tools 0.1.10, jobserver 0.1.34, js-sys 0.3.103, openssl-probe 0.2.1, pkg-config 0.3.33, socket2 0.6.4, wait-timeout 0.2.1, wasm-bindgen 0.2.126, wasm-bindgen-macro 0.2.126, wasm-bindgen-macro-support 0.2.126, wasm-bindgen-shared 0.2.126
|
||||
----------------------------------------------------------------------------
|
||||
Copyright (c) 2014 Alex Crichton
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
THIRD-PARTY SOFTWARE NOTICES
|
||||
============================================================================
|
||||
|
||||
punktfunk (https://git.unom.io/unom/punktfunk) is licensed under MIT OR Apache-2.0.
|
||||
Punktfunk (https://git.unom.io/unom/punktfunk) is licensed under MIT OR Apache-2.0.
|
||||
The binaries it ships statically/dynamically link the third-party Rust crates listed
|
||||
below. Each is distributed under its own permissive license; the full license texts
|
||||
follow the manifest. This file is generated by scripts/gen-third-party-notices.py
|
||||
@@ -60,7 +60,7 @@ MANIFEST (crate version — SPDX license — source)
|
||||
bytes 1.12.0 — MIT — https://github.com/tokio-rs/bytes
|
||||
cast 0.3.0 — MIT OR Apache-2.0 — https://github.com/japaric/cast.rs
|
||||
cbindgen 0.29.4 — MPL-2.0 — https://github.com/mozilla/cbindgen
|
||||
cc 1.2.65 — MIT OR Apache-2.0 — https://github.com/rust-lang/cc-rs
|
||||
cc 1.4.1 — MIT OR Apache-2.0 — https://github.com/rust-lang/cc-rs
|
||||
cesu8 1.1.0 — Apache-2.0/MIT — https://github.com/emk/cesu8-rs
|
||||
cexpr 0.6.0 — Apache-2.0/MIT — https://github.com/jethrogb/rust-cexpr
|
||||
cfg-expr 0.20.8 — MIT OR Apache-2.0 — https://github.com/EmbarkStudios/cfg-expr
|
||||
@@ -114,7 +114,7 @@ MANIFEST (crate version — SPDX license — source)
|
||||
fastrand 2.4.1 — Apache-2.0 OR MIT — https://github.com/smol-rs/fastrand
|
||||
fiat-crypto 0.2.9 — MIT OR Apache-2.0 OR BSD-1-Clause — https://github.com/mit-plv/fiat-crypto
|
||||
filetime 0.2.29 — MIT/Apache-2.0 — https://github.com/alexcrichton/filetime
|
||||
find-msvc-tools 0.1.9 — MIT OR Apache-2.0 — https://github.com/rust-lang/cc-rs
|
||||
find-msvc-tools 0.1.10 — MIT OR Apache-2.0 — https://github.com/rust-lang/cc-rs
|
||||
fixedbitset 0.5.7 — MIT OR Apache-2.0 — https://github.com/petgraph/fixedbitset
|
||||
flate2 1.1.9 — MIT OR Apache-2.0 — https://github.com/rust-lang/flate2-rs
|
||||
flume 0.12.0 — Apache-2.0/MIT — https://github.com/zesterer/flume
|
||||
@@ -1598,7 +1598,7 @@ DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
The following license (LICENSE-APACHE) applies to: assert_matches 1.5.0, async-channel 2.5.0, autocfg 1.5.1, base64 0.22.1, bitflags 1.3.2, bitflags 2.13.0, bumpalo 3.20.3, cast 0.3.0, cc 1.2.65, cexpr 0.6.0, cfg-if 1.0.4, cmake 0.1.58, concurrent-queue 2.5.0, core-foundation 0.10.1, core-foundation-sys 0.8.7, criterion 0.5.1, criterion-plot 0.5.0, crossbeam-deque 0.8.6, crossbeam-epoch 0.9.20, crossbeam-utils 0.8.21, curve25519-dalek-derive 0.1.1, displaydoc 0.2.6, either 1.16.0, equivalent 1.0.2, errno 0.3.14, event-listener 5.4.1, event-listener-strategy 0.5.4, fastrand 2.4.1, filetime 0.2.29, find-msvc-tools 0.1.9, fixedbitset 0.5.7, flate2 1.1.9, fnv 1.0.7, form_urlencoded 1.2.2, glob 0.3.3, hashbrown 0.17.1, heck 0.5.0, hermit-abi 0.5.2, idna 1.1.0, idna_adapter 1.2.2, indexmap 2.14.0, itertools 0.10.5, itertools 0.13.0, jni 0.21.1, jobserver 0.1.34, js-sys 0.3.103, lazy_static 1.5.0, linux-raw-sys 0.12.1, lock_api 0.4.14, log 0.4.33, nasm-rs 0.3.2, num-integer 0.1.46, num-traits 0.2.19, once_cell 1.21.4, openssl-probe 0.2.1, opus 0.3.1, parking 2.2.1, parking_lot 0.12.5, parking_lot_core 0.9.12, percent-encoding 2.3.2, pkg-config 0.3.33, proptest 1.11.0, rayon 1.12.0, rayon-core 1.13.0, regex 1.12.4, regex-automata 0.4.14, regex-syntax 0.8.11, rustc_version 0.4.1, rustix 1.1.4, rustls 0.23.41, rustls-native-certs 0.8.4, rusty-fork 0.3.1, scopeguard 1.2.0, security-framework 3.7.0, security-framework-sys 2.17.0, signal-hook-registry 1.4.8, smallvec 1.15.2, socket2 0.6.4, stable_deref_trait 1.2.1, system-deps 7.0.8, tar 0.4.46, tempfile 3.27.0, thread_local 1.1.9, tinytemplate 1.2.1, unicode-segmentation 1.13.3, unicode-width 0.2.2, url 2.5.8, vcpkg 0.2.15, version_check 0.9.5, wait-timeout 0.2.1, wasi 0.11.1+wasi-snapshot-preview1, wasip2 1.0.4+wasi-0.2.12, wasm-bindgen 0.2.126, wasm-bindgen-macro 0.2.126, wasm-bindgen-macro-support 0.2.126, wasm-bindgen-shared 0.2.126, wit-bindgen 0.57.1, xattr 1.6.1
|
||||
The following license (LICENSE-APACHE) applies to: assert_matches 1.5.0, async-channel 2.5.0, autocfg 1.5.1, base64 0.22.1, bitflags 1.3.2, bitflags 2.13.0, bumpalo 3.20.3, cast 0.3.0, cc 1.4.1, cexpr 0.6.0, cfg-if 1.0.4, cmake 0.1.58, concurrent-queue 2.5.0, core-foundation 0.10.1, core-foundation-sys 0.8.7, criterion 0.5.1, criterion-plot 0.5.0, crossbeam-deque 0.8.6, crossbeam-epoch 0.9.20, crossbeam-utils 0.8.21, curve25519-dalek-derive 0.1.1, displaydoc 0.2.6, either 1.16.0, equivalent 1.0.2, errno 0.3.14, event-listener 5.4.1, event-listener-strategy 0.5.4, fastrand 2.4.1, filetime 0.2.29, find-msvc-tools 0.1.10, fixedbitset 0.5.7, flate2 1.1.9, fnv 1.0.7, form_urlencoded 1.2.2, glob 0.3.3, hashbrown 0.17.1, heck 0.5.0, hermit-abi 0.5.2, idna 1.1.0, idna_adapter 1.2.2, indexmap 2.14.0, itertools 0.10.5, itertools 0.13.0, jni 0.21.1, jobserver 0.1.34, js-sys 0.3.103, lazy_static 1.5.0, linux-raw-sys 0.12.1, lock_api 0.4.14, log 0.4.33, nasm-rs 0.3.2, num-integer 0.1.46, num-traits 0.2.19, once_cell 1.21.4, openssl-probe 0.2.1, opus 0.3.1, parking 2.2.1, parking_lot 0.12.5, parking_lot_core 0.9.12, percent-encoding 2.3.2, pkg-config 0.3.33, proptest 1.11.0, rayon 1.12.0, rayon-core 1.13.0, regex 1.12.4, regex-automata 0.4.14, regex-syntax 0.8.11, rustc_version 0.4.1, rustix 1.1.4, rustls 0.23.41, rustls-native-certs 0.8.4, rusty-fork 0.3.1, scopeguard 1.2.0, security-framework 3.7.0, security-framework-sys 2.17.0, signal-hook-registry 1.4.8, smallvec 1.15.2, socket2 0.6.4, stable_deref_trait 1.2.1, system-deps 7.0.8, tar 0.4.46, tempfile 3.27.0, thread_local 1.1.9, tinytemplate 1.2.1, unicode-segmentation 1.13.3, unicode-width 0.2.2, url 2.5.8, vcpkg 0.2.15, version_check 0.9.5, wait-timeout 0.2.1, wasi 0.11.1+wasi-snapshot-preview1, wasip2 1.0.4+wasi-0.2.12, wasm-bindgen 0.2.126, wasm-bindgen-macro 0.2.126, wasm-bindgen-macro-support 0.2.126, wasm-bindgen-shared 0.2.126, wit-bindgen 0.57.1, xattr 1.6.1
|
||||
----------------------------------------------------------------------------
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
@@ -2853,7 +2853,7 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
The following license (LICENSE-MIT) applies to: cc 1.2.65, cfg-if 1.0.4, cmake 0.1.58, filetime 0.2.29, find-msvc-tools 0.1.9, jobserver 0.1.34, js-sys 0.3.103, openssl-probe 0.2.1, pkg-config 0.3.33, socket2 0.6.4, wait-timeout 0.2.1, wasm-bindgen 0.2.126, wasm-bindgen-macro 0.2.126, wasm-bindgen-macro-support 0.2.126, wasm-bindgen-shared 0.2.126
|
||||
The following license (LICENSE-MIT) applies to: cc 1.4.1, cfg-if 1.0.4, cmake 0.1.58, filetime 0.2.29, find-msvc-tools 0.1.10, jobserver 0.1.34, js-sys 0.3.103, openssl-probe 0.2.1, pkg-config 0.3.33, socket2 0.6.4, wait-timeout 0.2.1, wasm-bindgen 0.2.126, wasm-bindgen-macro 0.2.126, wasm-bindgen-macro-support 0.2.126, wasm-bindgen-shared 0.2.126
|
||||
----------------------------------------------------------------------------
|
||||
Copyright (c) 2014 Alex Crichton
|
||||
|
||||
|
||||
@@ -118,7 +118,11 @@ rand = "0.9"
|
||||
# need the hidapi driver). Linux links the system SDL3; Windows builds it from source
|
||||
# (no system SDL3 there — same choice as clients/windows).
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
pipewire = "0.9"
|
||||
# `v0_3_49` for `Buffer::requested` — the graph's per-cycle frame ask, without which the
|
||||
# playback callback can only size writes from the buffer CEILING (quantum-limit, ~170 ms).
|
||||
# Pure cfg gate; needs libpipewire ≥ 0.3.49 (2022-03) at runtime, which every ship target
|
||||
# (SteamOS, flatpak runtimes, Arch, Ubuntu ≥ 22.10) clears.
|
||||
pipewire = { version = "0.9", features = ["v0_3_49"] }
|
||||
sdl3 = { version = "0.18", features = ["hidapi"] }
|
||||
# Native VAAPI decode (M6 of the native-decode program): the hand-declared libva buffer
|
||||
# layouts, the profile/format/surface decisions, the AuPlan → picparams/IQ/slice
|
||||
|
||||
@@ -274,14 +274,41 @@ fn pw_thread(
|
||||
chunk.clear();
|
||||
let _ = ud.recycle.try_send(chunk);
|
||||
}
|
||||
// The graph asks for `requested` frames this cycle (one quantum, after
|
||||
// rate-matching); the mapped buffer is sized for the WORST case — PipeWire's
|
||||
// `quantum-limit`, 8192 frames ≈ 170 ms — not for this cycle. Filling to
|
||||
// capacity queued ~170 ms per buffer downstream of the ring and, worse, taught
|
||||
// the jitter policy that the device drains 170 ms per callback, which lifted
|
||||
// the underrun floor (`want` + one frame) above any depth the A/V sync loop is
|
||||
// allowed to ask for: audio sat a stable ~270 ms late and, by the continuity
|
||||
// rule, sync was FORBIDDEN from draining it. Capacity is only the ceiling;
|
||||
// `requested == 0` (no adapter suggestion) falls back to it.
|
||||
let requested = usize::try_from(buffer.requested()).unwrap_or(0);
|
||||
let stride = 4 * ud.channels; // F32LE interleaved
|
||||
let datas = buffer.datas_mut();
|
||||
if datas.is_empty() {
|
||||
return;
|
||||
}
|
||||
let data = &mut datas[0];
|
||||
let want_frames = data.data().map(|s| s.len() / stride).unwrap_or(0);
|
||||
let max_frames = data.data().map(|s| s.len() / stride).unwrap_or(0);
|
||||
let want_frames = if requested > 0 {
|
||||
requested.min(max_frames)
|
||||
} else {
|
||||
max_frames
|
||||
};
|
||||
let want = want_frames * ud.channels;
|
||||
// Once per stream, in the shape of the host's per-capture-open quantum log:
|
||||
// whether the graph's request or the buffer ceiling is sizing our writes is
|
||||
// exactly what an on-glass latency report needs to say.
|
||||
if ud.callbacks == 0 {
|
||||
tracing::info!(
|
||||
requested_frames = requested,
|
||||
capacity_frames = max_frames,
|
||||
write_frames = want_frames,
|
||||
write_ms = want_frames / 48,
|
||||
"audio playback quantum"
|
||||
);
|
||||
}
|
||||
|
||||
// A/V sync: take whatever depth the decode thread's sync loop last asked for, and
|
||||
// publish where the ring actually is so it can measure the result. The policy
|
||||
|
||||
@@ -35,8 +35,12 @@ pf-capture = { path = "../pf-capture" }
|
||||
openh264 = "0.9"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
# libavcodec (NVENC libav + VAAPI backends). `ffmpeg-sys-next` auto-detects the FFmpeg version.
|
||||
ffmpeg-next = "8"
|
||||
# libavcodec (NVENC libav + VAAPI backends). `ffmpeg-sys-next` auto-detects the FFmpeg version, so
|
||||
# this pin tracks the crate's own major (which shadows FFmpeg's): 9 = FFmpeg 9 (libavcodec 63,
|
||||
# libavutil 61). Arch shipped FFmpeg 9 on 2026-08-08 and every soname moved with it; the packaged
|
||||
# host must be BUILT against the FFmpeg it will run on, and packaging/arch/PKGBUILD now derives a
|
||||
# soname dep from that link so pacman can no longer walk an install across the break.
|
||||
ffmpeg-next = "9"
|
||||
libc = "0.2"
|
||||
# Vulkan bindings for the raw Vulkan-Video encode + PyroWave compute backends (feature-gated below;
|
||||
# the dep stays unconditional to mirror the host's Linux target — unused-but-declared is harmless).
|
||||
@@ -53,7 +57,7 @@ pyrowave-sys = { path = "../pyrowave-sys", optional = true }
|
||||
# NVENC (direct SDK, D3D11 input) + the shared D3D11/DXGI vocabulary via pf-frame.
|
||||
nvidia-video-codec-sdk = { version = "0.4", features = ["ci-check"], optional = true }
|
||||
# AMD (AMF) + Intel (QSV) hardware encode via libavcodec (behind `amf-qsv`; link-imports FFmpeg).
|
||||
ffmpeg-next = { version = "8", optional = true }
|
||||
ffmpeg-next = { version = "9", optional = true }
|
||||
# `libnvidia-encode`/`nvEncodeAPI64.dll` resolved at runtime; the NVENC status→cause table dlopen.
|
||||
libloading = "0.8"
|
||||
# Native Intel QSV (VPL): vendored static MIT dispatcher + bindgen'd C API, only under `qsv`.
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
//! NVENC encoder via `ffmpeg-next` (binds the system FFmpeg — `ffmpeg-sys-next` auto-detects the
|
||||
//! installed version, so this builds against FFmpeg 7.x/libavcodec 61 *or* 8.x/libavcodec 62;
|
||||
//! validated live on Ubuntu 26.04 (FFmpeg 8) and Bazzite F43 (FFmpeg 7.1)).
|
||||
//! installed version and emits a per-version cfg, so one source tree spans FFmpeg 7.x/libavcodec 61,
|
||||
//! 8.x/62 and 9.x/63; validated live on Ubuntu 26.04 (FFmpeg 8), Bazzite F43 (7.1) and CachyOS
|
||||
//! (FFmpeg 9). The `ffmpeg-next` MAJOR is a ceiling, not a target: 8.x refused anything past
|
||||
//! libavcodec 62, which is why Arch's FFmpeg 9 needed the crate bump and not just a rebuild.
|
||||
//! What a given package links is decided by the BUILDER's FFmpeg, so the soname bound that keeps an
|
||||
//! install honest is generated at package time — see packaging/arch/PKGBUILD.
|
||||
//!
|
||||
//! Input is a packed RGB/BGR CPU frame; `*_nvenc` accepts `rgb0`/`bgr0`/`rgba`/`bgra`
|
||||
//! directly and does the RGB→YUV conversion on the GPU, so the host stays off the
|
||||
|
||||
@@ -79,6 +79,13 @@ struct AVD3D11VADeviceContext {
|
||||
lock: *mut c_void, // void (*)(void*)
|
||||
unlock: *mut c_void, // void (*)(void*)
|
||||
lock_ctx: *mut c_void,
|
||||
// DELIBERATELY TRUNCATED: FFmpeg >=8 appends `UINT BindFlags; UINT MiscFlags;` here, FFmpeg 7.1
|
||||
// does not, and we build against both (Windows links the BtbN n7.1 tree, Linux the distro's 8 or
|
||||
// 9). Mirroring only the common prefix is what makes one definition correct for all three —
|
||||
// libav owns the allocation (av_hwdevice_ctx_alloc sizes it), and we only ever WRITE `device` at
|
||||
// offset 0, so a short mirror can never read or write past what libav allocated. Adding the two
|
||||
// flags to match 8/9 would silently mis-describe the 7.1 build we actually ship on Windows.
|
||||
// The per-pool AVD3D11VAFramesContext.BindFlags below — which we DO set — exists in all three.
|
||||
}
|
||||
|
||||
/// `AVD3D11VAFramesContext` (libavutil/hwcontext_d3d11va.h) — mirrored. `BindFlags`/`MiscFlags`
|
||||
@@ -95,10 +102,17 @@ struct AVD3D11VAFramesContext {
|
||||
// Hand-written mirrors of libav's `AVD3D11VADeviceContext` / `AVD3D11VAFramesContext`
|
||||
// (hwcontext_d3d11va.h) — `ffmpeg-sys-next` binds neither, and we WRITE `device` / `bind_flags`
|
||||
// through them, so a wrong offset is silent corruption of libav's context rather than a compile
|
||||
// error. ⚠ These two structs are duplicated in the other crate that talks to the same libav
|
||||
// contexts (pf-encode's `ffmpeg_win.rs` and pf-client-core's `video_d3d11.rs`); they must agree
|
||||
// with libav AND with each other, and these assertions are what makes a drift in either a build
|
||||
// failure instead of a runtime mystery.
|
||||
// error.
|
||||
//
|
||||
// ⚠ KNOW WHAT THESE ASSERTIONS DO AND DO NOT BUY YOU. They pin OUR layout, not libav's, so they
|
||||
// turn an accidental edit to the structs above into a build failure — but nothing here reads
|
||||
// hwcontext_d3d11va.h, so a field libav inserts upstream still sails straight through, and a green
|
||||
// build is not evidence. Both layouts were therefore re-checked BY HAND against FFmpeg 7.1, 8.1.2
|
||||
// and 9.0 during the 8 -> 9 bump (2026-08-08): `AVD3D11VAFramesContext` is byte-identical in all
|
||||
// three, and `AVD3D11VADeviceContext` gained two trailing UINTs in 8 that 7.1 lacks — which is
|
||||
// exactly why the struct above stops at the common prefix. Re-check by hand on the next FFmpeg
|
||||
// major. (An older note here claimed these were duplicated in pf-client-core's `video_d3d11.rs`;
|
||||
// that copy went away with the client's FFmpeg in M10, so this is now the only definition.)
|
||||
const _: () = {
|
||||
use std::mem::{offset_of, size_of};
|
||||
type P = *mut c_void;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
use super::dualsense_proto::DsState;
|
||||
use super::dualsense_windows::{
|
||||
create_swdevice, publish_input, OutputDrain, SwDeviceProfile, DEVTYPE_DUALSHOCK4, OFF_DEVTYPE,
|
||||
OFF_DRIVER_PROTO, OFF_OUT_RING_VER, OFF_PAD_INDEX, SHM_MAGIC, SHM_SIZE,
|
||||
OFF_DRIVER_PROTO, OFF_INPUT, OFF_OUT_RING_VER, OFF_PAD_INDEX, SHM_MAGIC, SHM_SIZE,
|
||||
};
|
||||
use super::dualshock4_proto::{
|
||||
parse_ds4_output, serialize_state, Ds4Feedback, DS4_INPUT_REPORT_LEN, DS4_TOUCH_H, DS4_TOUCH_W,
|
||||
|
||||
+31
-2
@@ -47,7 +47,11 @@ options=('!lto' '!debug')
|
||||
|
||||
# All build deps for both packages (Arch runtime packages ship their own headers, so these cover
|
||||
# build + link). aws-lc/ring need clang+cmake; nasm is for asm. ffmpeg stays because the HOST's
|
||||
# encoder links libav* — the CLIENT dropped it in M10 and decodes natively. No vulkan-headers:
|
||||
# encoder links libav* — the CLIENT dropped it in M10 and decodes natively. `ffmpeg` is
|
||||
# deliberately UNVERSIONED here: ffmpeg-sys-next auto-detects the installed FFmpeg, so the package
|
||||
# builds against whatever the builder ships, and the RUNTIME bound follows automatically from the
|
||||
# soname deps makepkg derives from the linked binary (see package_punktfunk-host's depends). Pinning
|
||||
# a version at build time would just have to be re-edited on every FFmpeg major. No vulkan-headers:
|
||||
# nothing in the workspace compiles against the system Vulkan headers any more (pyrowave-sys builds
|
||||
# against its own vendored copy, and both binaries reach Vulkan through ash, which dlopens it).
|
||||
makedepends=('rust' 'cargo' 'clang' 'cmake' 'nasm' 'pkgconf' 'git'
|
||||
@@ -135,8 +139,33 @@ package_punktfunk-host() {
|
||||
# opens a Pulse socket itself, so pipewire-pulse is an OPTdepend, not a depend: it exists
|
||||
# for the GAMES, which commonly emit through the PulseAudio API. Hard-depending on it made
|
||||
# the package uninstallable next to real `pulseaudio`, which serves those games just as well.
|
||||
# ⚠ The libav* entries below are SONAME deps, not package names, and they are load-bearing.
|
||||
# pacman is the only one of our packaging formats that does NOT derive dependencies from ELF
|
||||
# DT_NEEDED (rpm auto-generates `libavcodec.so.62()(64bit)`; dpkg-shlibdeps emits `libavcodec62`;
|
||||
# nix pins the closure). So a bare `depends=('ffmpeg')` let `pacman -Syu` walk the host across an
|
||||
# FFmpeg soname bump with no warning and no conflict — which is exactly what FFmpeg 8 -> 9
|
||||
# (2026-08-08, ffmpeg 2:9.0-5: libavutil .60->.61, libavcodec .62->.63, libavfilter .11->.12,
|
||||
# libavdevice .62->.63, libswscale .9->.10) did to every Arch/CachyOS user: the dynamic loader
|
||||
# cannot start the binary at all, so it is exit 127 *before* main() in a systemd restart loop,
|
||||
# with nothing in the host's own log to explain it (`ldd /usr/bin/punktfunk-host | grep "not found"`
|
||||
# is the one-line diagnosis). Arch's ffmpeg declares the matching
|
||||
# `provides=(libavcodec.so=63-64 libavutil.so=61-64 ...)`, and makepkg rewrites each bare
|
||||
# `libfoo.so` listed here into `libfoo.so=<soname>-<arch>` by reading the built binary's
|
||||
# DT_NEEDED — so the bound tracks whatever FFmpeg the builder linked against, with nothing to
|
||||
# hand-maintain across the next bump. pacman then REFUSES the ffmpeg upgrade instead of bricking
|
||||
# the install, and the canary rebuilt on an ffmpeg-9 image installs cleanly on an ffmpeg-9 box.
|
||||
# A hand-written `ffmpeg<2:9` would have to be edited (and would go stale) on every bump; this
|
||||
# does not. NOT bundling FFmpeg the way the .deb does (BUNDLE_FFMPEG=1): that exists because
|
||||
# Ubuntu 24.04 LTS is frozen on FFmpeg 6.1 and can never satisfy the dep, whereas rolling Arch
|
||||
# always ships a current FFmpeg — and vendoring a second copy of a system library is against
|
||||
# Arch packaging doctrine (and would not survive AUR review).
|
||||
# All seven are listed even though --as-needed currently drops libavformat/libswresample from the
|
||||
# link: an unlinked soname is left bare by makepkg and is satisfied by any ffmpeg, so listing it
|
||||
# costs nothing, while a future link picking one up gets the version bound automatically.
|
||||
depends=('ffmpeg' 'pipewire' 'wireplumber' 'opus' 'libei'
|
||||
'mesa' 'libglvnd' 'libxkbcommon' 'wayland')
|
||||
'mesa' 'libglvnd' 'libxkbcommon' 'wayland'
|
||||
'libavcodec.so' 'libavutil.so' 'libavfilter.so' 'libavdevice.so'
|
||||
'libavformat.so' 'libswscale.so' 'libswresample.so')
|
||||
optdepends=('pipewire-pulse: PulseAudio-API audio from games/apps (real `pulseaudio` also works)'
|
||||
'nvidia-utils: NVENC hardware encode + GPU EGL/CUDA zero-copy (REQUIRED to encode on NVIDIA)'
|
||||
'gamescope: per-session nested compositor backend (no desktop login needed) — needs >=3.16.22'
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.5.7/schema.json",
|
||||
"files": {
|
||||
"ignoreUnknown": false,
|
||||
"includes": ["**", "!dist", "!examples/**/dist", "!**/node_modules"]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "tab"
|
||||
},
|
||||
"css": {
|
||||
"parser": {
|
||||
"tailwindDirectives": true
|
||||
}
|
||||
},
|
||||
"assist": {
|
||||
"actions": {
|
||||
"source": {
|
||||
"organizeImports": "on"
|
||||
}
|
||||
}
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"preset": "recommended",
|
||||
"suspicious": {
|
||||
"noArrayIndexKey": "off"
|
||||
},
|
||||
"style": {
|
||||
"noNonNullAssertion": "off"
|
||||
},
|
||||
"a11y": {
|
||||
"noLabelWithoutControl": "off"
|
||||
},
|
||||
"correctness": {
|
||||
"useExhaustiveDependencies": "warn"
|
||||
}
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"quoteStyle": "double"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
"": {
|
||||
"name": "@punktfunk/plugin-kit",
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.5.2",
|
||||
"@punktfunk/host": "file:../sdk",
|
||||
"@types/bun": "^1.3.0",
|
||||
"@types/react": "^19.2.16",
|
||||
@@ -25,6 +26,24 @@
|
||||
"undici": "^8.9.0",
|
||||
},
|
||||
"packages": {
|
||||
"@biomejs/biome": ["@biomejs/biome@2.5.7", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.5.7", "@biomejs/cli-darwin-x64": "2.5.7", "@biomejs/cli-linux-arm64": "2.5.7", "@biomejs/cli-linux-arm64-musl": "2.5.7", "@biomejs/cli-linux-x64": "2.5.7", "@biomejs/cli-linux-x64-musl": "2.5.7", "@biomejs/cli-win32-arm64": "2.5.7", "@biomejs/cli-win32-x64": "2.5.7" }, "bin": { "biome": "bin/biome" } }, "sha512-zr8K/DcY5tYsQOQwqMJ0AWElo6QgmgNI7idXgXLhevVszlt8RGVpesEJPqx3ThazLaOwjJ5Y8fz3BtH5fGZNsw=="],
|
||||
|
||||
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.5.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-vxo/Ls3/PYdQWyLhYYcgMOCzQypAjcY+iihS8M0wW03l16TCLW4zqZzGo75gm1VdCMj38hTVZ31KBWrZ4G9dJw=="],
|
||||
|
||||
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.5.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-Cd3Ga61amT/Yl/0x8elP5hhGYaFy4bw6WuysTgf7oo8TA5tJ5A1k+DkVoJ2BHbTVil51gTX9VPzArnrlLJ3Kyg=="],
|
||||
|
||||
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.5.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-rR2QE0yF2GYSuYuKIa7pKvODGJqnOH+2eDREAM8wV+mWKSkMQKdAp4zXEZfTaxY8PMoNONnpgSWcBCyLDPDOKg=="],
|
||||
|
||||
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.5.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-xPI5yB6XlpDbNkS+bm1t42olw5c4l3UrlOmLg7KtLJvjvkNF/1V4tnUgfkylGIeb3u/T+BzMGYqgQhzjAoJzuQ=="],
|
||||
|
||||
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.5.7", "", { "os": "linux", "cpu": "x64" }, "sha512-FQgqJhscrqJUFptGaRSUJWlXAExwWcDwLuK49dvKfkQ1bB5SEEyFssnsxQY83Xm6jR0EbbX3+8+D5bfvYqUG2Q=="],
|
||||
|
||||
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.5.7", "", { "os": "linux", "cpu": "x64" }, "sha512-rE5VZi+qtmPgQH+l7jVxYoZ18b/TiHEhulhMpjmCZH1PltSbjRcxNWywC3HZ9tYottG7ORkeTtoscBilKSBm0g=="],
|
||||
|
||||
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.5.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-Oq4x0CCwP4jirrcTywXs5kOGZ4v5vuEP+gWrbtjApOA2CL9F3F9GlIdQIci8AKSCa/zURanMRpX/4wQ7Am6hHg=="],
|
||||
|
||||
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.5.7", "", { "os": "win32", "cpu": "x64" }, "sha512-V+0wu/nrj2S+MhP4EQ0uHNolP0IALEsz45pg0WoKkHfDeh0+ItHwP/p7bX5RPoMOl9NkpHYWdYPhIcy2mACHvQ=="],
|
||||
|
||||
"@effect/openapi-generator": ["@effect/openapi-generator@4.0.0-beta.98", "", { "dependencies": { "swagger2openapi": "^7.0.8" }, "peerDependencies": { "@effect/platform-node": "^4.0.0-beta.98", "effect": "^4.0.0-beta.98" }, "bin": { "openapigen": "dist/bin.js" } }, "sha512-7bqawr/HqJWqQ8H/bHyzBlLPA3LIIm3Y+cGYlIxnC/QVK795QpiEXb7uxTnP7V7w49V0sBtTerv4/9ZjsMffLQ=="],
|
||||
|
||||
"@effect/platform-node": ["@effect/platform-node@4.0.0-beta.98", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.98", "mime": "^4.1.0", "undici": "^8.7.0" }, "peerDependencies": { "effect": "^4.0.0-beta.98", "ioredis": "^5.7.0" } }, "sha512-IQu1TiLXQEDSGkDBllyYjVadf+UqdjptryqX4mmktVTTbGDq7X4uVxe7cSgXuqZvyfG6kagTzwj2lfynxOaKQg=="],
|
||||
@@ -49,6 +68,8 @@
|
||||
|
||||
"@punktfunk/host": ["@punktfunk/host@file:../sdk", { "devDependencies": { "@effect/openapi-generator": "4.0.0-beta.98", "@effect/platform-node": "4.0.0-beta.98", "@types/bun": "^1.3.0", "bun2nix": "2.1.2", "effect": "^4.0.0-beta.98", "typescript": "^5.9.3" }, "optionalDependencies": { "undici": "^7.0.0" }, "peerDependencies": { "effect": "^4.0.0-beta.98" }, "bin": { "punktfunk-scripting": "./dist/runner-cli.js" } }],
|
||||
|
||||
"@punktfunk/host": ["@punktfunk/host@file:../sdk", { "devDependencies": { "@effect/openapi-generator": "4.0.0-beta.98", "@effect/platform-node": "4.0.0-beta.98", "@types/bun": "^1.3.0", "bun2nix": "2.1.2", "effect": "^4.0.0-beta.98", "typescript": "^5.9.3" }, "optionalDependencies": { "undici": "^7.0.0" }, "peerDependencies": { "effect": "^4.0.0-beta.98" }, "bin": { "punktfunk-scripting": "./dist/runner-cli.js" } }],
|
||||
|
||||
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
|
||||
|
||||
@@ -55,9 +55,10 @@ const databaseCandidates = (): string[] => {
|
||||
};
|
||||
|
||||
const findDatabase = (cfg: { databasePath?: string }): string | undefined =>
|
||||
[...(cfg.databasePath ? [cfg.databasePath] : []), ...databaseCandidates()].find(
|
||||
isFile,
|
||||
);
|
||||
[
|
||||
...(cfg.databasePath ? [cfg.databasePath] : []),
|
||||
...databaseCandidates(),
|
||||
].find(isFile);
|
||||
|
||||
/**
|
||||
* `<kind>/<slug>.jpg` across the current, legacy-cache and Flatpak Lutris roots.
|
||||
|
||||
+14
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@punktfunk/plugin-kit",
|
||||
"version": "0.3.1",
|
||||
"version": "0.3.2",
|
||||
"description": "Effect-based framework for punktfunk plugins: lifecycle runtime, config/state, sync engine, UI serving, CLI scaffold, and browser helpers.",
|
||||
"type": "module",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
@@ -13,7 +13,12 @@
|
||||
"bugs": {
|
||||
"url": "https://git.unom.io/unom/punktfunk/issues"
|
||||
},
|
||||
"keywords": ["punktfunk", "plugin", "framework", "effect"],
|
||||
"keywords": [
|
||||
"punktfunk",
|
||||
"plugin",
|
||||
"framework",
|
||||
"effect"
|
||||
],
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
@@ -35,7 +40,10 @@
|
||||
},
|
||||
"./theme.css": "./dist/theme.css"
|
||||
},
|
||||
"files": ["dist", "README.md"],
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md"
|
||||
],
|
||||
"publishConfig": {
|
||||
"registry": "https://git.unom.io/api/packages/unom/npm/"
|
||||
},
|
||||
@@ -43,7 +51,8 @@
|
||||
"typecheck": "tsc --noEmit",
|
||||
"build": "tsc -p tsconfig.build.json && cp src/theme.css dist/theme.css",
|
||||
"test": "bun test",
|
||||
"prepublishOnly": "bun run build"
|
||||
"prepublishOnly": "bun run build",
|
||||
"check": "biome check ."
|
||||
},
|
||||
"peerDependencies": {
|
||||
"effect": "^4.0.0-beta.98",
|
||||
@@ -56,6 +65,7 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.5.2",
|
||||
"@punktfunk/host": "file:../sdk",
|
||||
"@types/bun": "^1.3.0",
|
||||
"@types/react": "^19.2.16",
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
import * as fs from "node:fs";
|
||||
import { Effect, Ref, Schema } from "effect";
|
||||
import type { ConfigWriteError } from "./errors.js";
|
||||
import { atomicWriteFile, ensureStateDir, statePath } from "./paths.js";
|
||||
import { PluginInfo } from "./host-client.js";
|
||||
import { atomicWriteFile, ensureStateDir, statePath } from "./paths.js";
|
||||
|
||||
export interface CacheStore<S extends Schema.Top> {
|
||||
readonly get: Effect.Effect<S["Type"]>;
|
||||
@@ -44,20 +44,14 @@ export const makeCacheStore = <S extends Schema.Top>(opts: {
|
||||
|
||||
const persist = (value: S["Type"]) =>
|
||||
ensureStateDir(info.name).pipe(
|
||||
Effect.flatMap(() =>
|
||||
atomicWriteFile(file, JSON.stringify(value)),
|
||||
),
|
||||
Effect.flatMap(() => atomicWriteFile(file, JSON.stringify(value))),
|
||||
);
|
||||
|
||||
const modify = <A>(f: (current: S["Type"]) => readonly [A, S["Type"]]) =>
|
||||
Ref.modify(ref, (current) => {
|
||||
const [a, next] = f(current);
|
||||
return [[a, next] as const, next] as const;
|
||||
}).pipe(
|
||||
Effect.flatMap(([a, next]) =>
|
||||
persist(next).pipe(Effect.as(a)),
|
||||
),
|
||||
);
|
||||
}).pipe(Effect.flatMap(([a, next]) => persist(next).pipe(Effect.as(a))));
|
||||
|
||||
return {
|
||||
get: Ref.get(ref),
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
// ManagedRuntime + layer graph as the plugin entry, so commands reuse the exact services.
|
||||
import { connect, type Punktfunk } from "@punktfunk/host";
|
||||
import { Effect, Layer, ManagedRuntime } from "effect";
|
||||
import { HostRequestError } from "./errors.js";
|
||||
import {
|
||||
type HostClient,
|
||||
hostClientFromFacade,
|
||||
type PluginInfo,
|
||||
pluginInfoLayer,
|
||||
} from "./host-client.js";
|
||||
import { HostRequestError } from "./errors.js";
|
||||
import { loggingLayer } from "./logging.js";
|
||||
import type { PluginKitDef } from "./runtime.js";
|
||||
|
||||
@@ -62,9 +62,7 @@ export const runPluginCli = async <E, R>(opts: {
|
||||
process.exit(name === undefined || name === "help" ? 0 : 2);
|
||||
}
|
||||
|
||||
const pf = command.offline
|
||||
? offlineFacade(opts.def.name)
|
||||
: await connect();
|
||||
const pf = command.offline ? offlineFacade(opts.def.name) : await connect();
|
||||
const base = Layer.mergeAll(
|
||||
hostClientFromFacade(pf),
|
||||
pluginInfoLayer({ name: opts.def.name, version: opts.def.version }),
|
||||
@@ -82,9 +80,7 @@ export const runPluginCli = async <E, R>(opts: {
|
||||
process.exitCode ??= 0;
|
||||
} catch (e) {
|
||||
const hint =
|
||||
e instanceof HostRequestError
|
||||
? " (is the Punktfunk host running?)"
|
||||
: "";
|
||||
e instanceof HostRequestError ? " (is the Punktfunk host running?)" : "";
|
||||
console.error(`${opts.def.name}: ${name} failed: ${e}${hint}`);
|
||||
process.exitCode = 1;
|
||||
} finally {
|
||||
|
||||
@@ -16,8 +16,8 @@ import {
|
||||
ConfigPermissionError,
|
||||
type ConfigWriteError,
|
||||
} from "./errors.js";
|
||||
import { atomicWriteFile, ensureStateDir, statePath } from "./paths.js";
|
||||
import { PluginInfo } from "./host-client.js";
|
||||
import { atomicWriteFile, ensureStateDir, statePath } from "./paths.js";
|
||||
|
||||
export interface ConfigService<S extends Schema.Top> {
|
||||
/** Decode the raw file with Schema defaults applied. Missing file → all defaults. */
|
||||
@@ -36,10 +36,7 @@ export interface ConfigService<S extends Schema.Top> {
|
||||
*/
|
||||
readonly saveRaw: (
|
||||
raw: unknown,
|
||||
) => Effect.Effect<
|
||||
S["Type"],
|
||||
ConfigParseError | ConfigWriteError
|
||||
>;
|
||||
) => Effect.Effect<S["Type"], ConfigParseError | ConfigWriteError>;
|
||||
/** Emits the decoded config after every successful `saveRaw`. */
|
||||
readonly changes: Stream.Stream<S["Type"]>;
|
||||
/** Absolute path of the config file (status views). */
|
||||
@@ -95,9 +92,7 @@ export const makeConfigService = <S extends Schema.Top>(opts: {
|
||||
const file = statePath(info.name, opts.fileName ?? "config.json");
|
||||
const hub = yield* PubSub.unbounded<S["Type"]>();
|
||||
|
||||
const decode = (
|
||||
raw: unknown,
|
||||
): Effect.Effect<S["Type"], ConfigParseError> =>
|
||||
const decode = (raw: unknown): Effect.Effect<S["Type"], ConfigParseError> =>
|
||||
Schema.decodeUnknownEffect(opts.schema)(raw).pipe(
|
||||
Effect.mapError(
|
||||
(e) => new ConfigParseError({ path: file, issue: String(e) }),
|
||||
|
||||
@@ -21,14 +21,13 @@ export interface HostClientService {
|
||||
readonly facade: Punktfunk;
|
||||
}
|
||||
|
||||
export class HostClient extends Context.Service<HostClient, HostClientService>()(
|
||||
"@punktfunk/plugin-kit/HostClient",
|
||||
) {}
|
||||
export class HostClient extends Context.Service<
|
||||
HostClient,
|
||||
HostClientService
|
||||
>()("@punktfunk/plugin-kit/HostClient") {}
|
||||
|
||||
/** Wrap the facade the runner hands to `main` (or `connect()` in the CLI/dev paths). */
|
||||
export const hostClientFromFacade = (
|
||||
pf: Punktfunk,
|
||||
): Layer.Layer<HostClient> =>
|
||||
export const hostClientFromFacade = (pf: Punktfunk): Layer.Layer<HostClient> =>
|
||||
Layer.succeed(HostClient)({
|
||||
request: (method, path, body) =>
|
||||
Effect.tryPromise({
|
||||
@@ -44,9 +43,10 @@ export interface PluginInfoService {
|
||||
readonly version?: string;
|
||||
}
|
||||
|
||||
export class PluginInfo extends Context.Service<PluginInfo, PluginInfoService>()(
|
||||
"@punktfunk/plugin-kit/PluginInfo",
|
||||
) {}
|
||||
export class PluginInfo extends Context.Service<
|
||||
PluginInfo,
|
||||
PluginInfoService
|
||||
>()("@punktfunk/plugin-kit/PluginInfo") {}
|
||||
|
||||
export const pluginInfoLayer = (
|
||||
info: PluginInfoService,
|
||||
|
||||
+15
-14
@@ -1,5 +1,18 @@
|
||||
// @punktfunk/plugin-kit — Effect-based framework for punktfunk plugins.
|
||||
|
||||
export { type CacheStore, makeCacheStore } from "./cache-store.js";
|
||||
export { type CliCommand, runPluginCli } from "./cli.js";
|
||||
export { type ConfigService, makeConfigService } from "./config.js";
|
||||
export * from "./errors.js";
|
||||
export {
|
||||
HostClient,
|
||||
type HostClientService,
|
||||
hostClientFromFacade,
|
||||
PluginInfo,
|
||||
type PluginInfoService,
|
||||
pluginInfoLayer,
|
||||
} from "./host-client.js";
|
||||
export { loggingLayer } from "./logging.js";
|
||||
export {
|
||||
atomicWriteFile,
|
||||
ensureStateDir,
|
||||
@@ -7,17 +20,6 @@ export {
|
||||
pluginStateDir,
|
||||
statePath,
|
||||
} from "./paths.js";
|
||||
export {
|
||||
HostClient,
|
||||
hostClientFromFacade,
|
||||
type HostClientService,
|
||||
PluginInfo,
|
||||
pluginInfoLayer,
|
||||
type PluginInfoService,
|
||||
} from "./host-client.js";
|
||||
export { loggingLayer } from "./logging.js";
|
||||
export { type ConfigService, makeConfigService } from "./config.js";
|
||||
export { type CacheStore, makeCacheStore } from "./cache-store.js";
|
||||
export {
|
||||
Artwork,
|
||||
DetectHint,
|
||||
@@ -33,6 +35,7 @@ export {
|
||||
type PluginKitDef,
|
||||
runPluginKitDirect,
|
||||
} from "./runtime.js";
|
||||
export { type SseRouteOptions, sseRoute } from "./sse.js";
|
||||
export {
|
||||
type LastSync,
|
||||
makeSyncEngine,
|
||||
@@ -47,9 +50,7 @@ export {
|
||||
deriveConfigJsonSchema,
|
||||
httpApiEnv,
|
||||
makeConfigHandler,
|
||||
serveUi,
|
||||
type ServeUiConfig,
|
||||
type ServeUiOptions,
|
||||
serveUi,
|
||||
} from "./ui-server.js";
|
||||
export { sseRoute, type SseRouteOptions } from "./sse.js";
|
||||
export { type CliCommand, runPluginCli } from "./cli.js";
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
// appending launcher entries, serving `__config` so the console renders settings without the plugin
|
||||
// shipping an SPA, registering under `category: "library"` so it stays out of the nav, and the
|
||||
// standard CLI verbs.
|
||||
import type { PluginDef } from "@punktfunk/host";
|
||||
|
||||
import * as fs from "node:fs";
|
||||
import { Duration, Effect, Layer, Schema, Stream } from "effect";
|
||||
import type { PluginDef } from "@punktfunk/host";
|
||||
import { Duration, Effect, type Schema, Stream } from "effect";
|
||||
import { type CliCommand, runPluginCli } from "../cli.js";
|
||||
import { type ConfigService, makeConfigService } from "../config.js";
|
||||
import { HostClient, PluginInfo } from "../host-client.js";
|
||||
import { HostClient, type PluginInfo } from "../host-client.js";
|
||||
import { ProviderClient, type ProviderClientService } from "../reconcile.js";
|
||||
import { definePluginKit, type PluginKitDef } from "../runtime.js";
|
||||
import { makeSyncEngine } from "../sync-engine.js";
|
||||
@@ -103,8 +104,11 @@ export const defineLibraryPlugin = <S extends Schema.Top>(
|
||||
const debounce = def.debounce ?? Duration.seconds(3);
|
||||
|
||||
/** The config service, built fresh wherever it is needed (it only requires `PluginInfo`). */
|
||||
const config: Effect.Effect<ConfigService<S>, never, PluginInfo> =
|
||||
makeConfigService({ schema: def.configSchema });
|
||||
const config: Effect.Effect<
|
||||
ConfigService<S>,
|
||||
never,
|
||||
PluginInfo
|
||||
> = makeConfigService({ schema: def.configSchema });
|
||||
|
||||
/** Scan + launcher entries, in the order they should reach the host. */
|
||||
const computeEntries = (
|
||||
@@ -234,7 +238,8 @@ export const defineLibraryPlugin = <S extends Schema.Top>(
|
||||
}),
|
||||
},
|
||||
scan: {
|
||||
summary: "scan and print what WOULD be synced (--preview for the JSON entries)",
|
||||
summary:
|
||||
"scan and print what WOULD be synced (--preview for the JSON entries)",
|
||||
// Also offline: the point is to debug a scanner against real launcher files without
|
||||
// touching the host's library.
|
||||
offline: true,
|
||||
@@ -289,9 +294,9 @@ export const defineLibraryPlugin = <S extends Schema.Top>(
|
||||
}
|
||||
const baseline = yield* Effect.try({
|
||||
try: () =>
|
||||
JSON.parse(fs.readFileSync(compare as string, "utf8")) as ReturnType<
|
||||
typeof fromHostEntry
|
||||
>[],
|
||||
JSON.parse(
|
||||
fs.readFileSync(compare as string, "utf8"),
|
||||
) as ReturnType<typeof fromHostEntry>[],
|
||||
catch: (cause) => new Error(`cannot read ${compare}: ${cause}`),
|
||||
});
|
||||
const cfg = yield* (yield* config).load;
|
||||
@@ -307,7 +312,8 @@ export const defineLibraryPlugin = <S extends Schema.Top>(
|
||||
}),
|
||||
},
|
||||
uninstall: {
|
||||
summary: "remove this source's games from the host and release its store claim",
|
||||
summary:
|
||||
"remove this source's games from the host and release its store claim",
|
||||
run: () =>
|
||||
Effect.gen(function* () {
|
||||
const provider = yield* ProviderClient;
|
||||
|
||||
@@ -36,7 +36,10 @@ export const fileUrl = (p: string): string => {
|
||||
* re-hashed; newer ones serve from an unpredictable per-asset-hash path, where this 404s and the
|
||||
* client falls through to its next candidate. That degradation is intentional and pre-existing.
|
||||
*/
|
||||
export const steamCdnUrl = (appid: number, kind: ArtKind): string | undefined => {
|
||||
export const steamCdnUrl = (
|
||||
appid: number,
|
||||
kind: ArtKind,
|
||||
): string | undefined => {
|
||||
// A non-Steam shortcut's appid has the high bit set and is never a real store appid — the CDN
|
||||
// would only 404, so don't emit a URL that is guaranteed to fail.
|
||||
if ((appid & 0x8000_0000) !== 0) return undefined;
|
||||
@@ -81,7 +84,12 @@ export const findLocalArtFile = (
|
||||
}
|
||||
// Older Steam wrote the files directly under `librarycache/` with the appid in the name.
|
||||
for (const name of localFilenames(kind)) {
|
||||
const flat = path.join(root, "appcache", "librarycache", `${appid}_${name}`);
|
||||
const flat = path.join(
|
||||
root,
|
||||
"appcache",
|
||||
"librarycache",
|
||||
`${appid}_${name}`,
|
||||
);
|
||||
if (isFile(flat)) return flat;
|
||||
}
|
||||
return undefined;
|
||||
|
||||
@@ -101,7 +101,7 @@ export const confinedJoin = (base: string, rel: string): string | undefined => {
|
||||
// Normalize separators so a Windows-shaped relative path is checked on any platform (a plugin
|
||||
// may parse a Windows manifest while its tests run on Linux).
|
||||
const parts = rel.split(/[\\/]/);
|
||||
if (parts[0] === "" ) return undefined; // rooted
|
||||
if (parts[0] === "") return undefined; // rooted
|
||||
if (/^[A-Za-z]:$/.test(parts[0])) return undefined; // drive prefix
|
||||
if (parts.some((p) => p === "..")) return undefined; // traversal
|
||||
const joined = path.join(base, ...parts.filter((p) => p !== "" && p !== "."));
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
// SSRF pivot from a process running on the operator's box (`http://169.254.169.254/…`, an internal
|
||||
// service). The host learned this in the 2026-07-17 security review; a plugin fetching the same
|
||||
// class of URL inherits the same rule. A rare legitimately-redirecting CDN just yields no art.
|
||||
import { HostRequestError } from "../../errors.js";
|
||||
|
||||
import { Effect } from "effect";
|
||||
import { HostRequestError } from "../../errors.js";
|
||||
|
||||
export interface FetchLimits {
|
||||
/** Hard cap on the response body. Default 8 MiB — a cover never approaches it. */
|
||||
|
||||
@@ -32,13 +32,13 @@ export {
|
||||
} from "./http.js";
|
||||
export {
|
||||
parseRegQuery,
|
||||
parseRegSubKeys,
|
||||
type RegValue,
|
||||
regQueryValue,
|
||||
regQueryValues,
|
||||
regSubKeys,
|
||||
type RegValue,
|
||||
validRegKey,
|
||||
} from "./registry.js";
|
||||
export { openReadOnly, type ReadOnlyDb, withReadOnlyDb } from "./sqlite.js";
|
||||
export {
|
||||
crc32,
|
||||
parseShortcuts,
|
||||
@@ -46,6 +46,7 @@ export {
|
||||
shortcutAppId,
|
||||
shortcutGameId,
|
||||
} from "./shortcuts.js";
|
||||
export { openReadOnly, type ReadOnlyDb, withReadOnlyDb } from "./sqlite.js";
|
||||
export {
|
||||
steamLibraryDirs,
|
||||
steamRoots,
|
||||
|
||||
@@ -59,17 +59,46 @@ export const regQueryValue = (key: string, name: string): string | undefined =>
|
||||
regQueryValues(key).find((v) => v.name.toLowerCase() === name.toLowerCase())
|
||||
?.data;
|
||||
|
||||
/** The immediate SUBKEY paths under one HKLM key (GOG lists one subkey per installed game). */
|
||||
/**
|
||||
* `reg.exe` always echoes the FULL hive name in its output rows, never the abbreviation it was
|
||||
* given: query `HKLM\SOFTWARE\…` and every line comes back `HKEY_LOCAL_MACHINE\SOFTWARE\…`.
|
||||
*/
|
||||
const HKLM_FULL = "HKEY_LOCAL_MACHINE\\";
|
||||
|
||||
/**
|
||||
* Parse `reg.exe query <key>` output into the immediate subkey NAMES under `key`.
|
||||
*
|
||||
* Exported for tests, like {@link parseRegQuery}, and for the same reason — this is a text format
|
||||
* that quietly breaks, and it did: the previous version matched output lines against the
|
||||
* abbreviated `HKLM\…` prefix it was handed, while reg.exe prints `HKEY_LOCAL_MACHINE\…`. Nothing
|
||||
* ever matched, so it returned `[]` on every machine, forever, and the one plugin that uses it
|
||||
* (GOG) reported "no games installed" instead of failing. See the regSubKeys tests.
|
||||
*
|
||||
* Returns NAMES, not paths: the sole consumer composes `${key}\\${name}`, and a GOG subkey name IS
|
||||
* the product id that becomes the entry's `external_id`.
|
||||
*/
|
||||
export const parseRegSubKeys = (stdout: string, key: string): string[] => {
|
||||
const full = key.toUpperCase().startsWith(HKLM)
|
||||
? HKLM_FULL + key.slice(HKLM.length)
|
||||
: key;
|
||||
const prefix = `${full.toLowerCase()}\\`;
|
||||
return (
|
||||
stdout
|
||||
.split(/\r?\n/)
|
||||
.map((l) => l.trim())
|
||||
.filter((l) => l.toLowerCase().startsWith(prefix))
|
||||
.map((l) => l.slice(full.length + 1))
|
||||
// Immediate children only — a deeper path still starts with the prefix.
|
||||
.filter((name) => name !== "" && !name.includes("\\"))
|
||||
);
|
||||
};
|
||||
|
||||
/** The immediate SUBKEY NAMES under one HKLM key (GOG lists one subkey per installed game). */
|
||||
export const regSubKeys = (key: string): string[] => {
|
||||
if (!validRegKey(key)) return [];
|
||||
const out = run(["query", key]);
|
||||
if (out === undefined) return [];
|
||||
const prefix = `${key.toLowerCase()}\\`;
|
||||
return out
|
||||
.split(/\r?\n/)
|
||||
.map((l) => l.trim())
|
||||
.filter((l) => l.toLowerCase().startsWith(prefix))
|
||||
.filter((l) => !l.slice(key.length + 1).includes("\\"));
|
||||
return parseRegSubKeys(out, key);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -40,7 +40,10 @@ const readCStr = (buf: Uint8Array, c: Cursor): string | undefined => {
|
||||
/** Read a little-endian int32, advancing 4 bytes. `undefined` if fewer than 4 remain. */
|
||||
const readI32 = (buf: Uint8Array, c: Cursor): number | undefined => {
|
||||
if (c.pos + 4 > buf.length) return undefined;
|
||||
const v = new DataView(buf.buffer, buf.byteOffset + c.pos, 4).getInt32(0, true);
|
||||
const v = new DataView(buf.buffer, buf.byteOffset + c.pos, 4).getInt32(
|
||||
0,
|
||||
true,
|
||||
);
|
||||
c.pos += 4;
|
||||
return v;
|
||||
};
|
||||
@@ -134,7 +137,7 @@ export const crc32 = (data: Uint8Array): number => {
|
||||
crc = (crc >>> 1) ^ (0xedb8_8320 & mask);
|
||||
}
|
||||
}
|
||||
return (~crc) >>> 0;
|
||||
return ~crc >>> 0;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
// matching the format the scripting runner journals (and what the previous hand-rolled
|
||||
// plugin loggers emitted), so kit-based plugins read consistently in
|
||||
// `journalctl --user -u punktfunk-scripting`.
|
||||
import { Cause, Layer, Logger } from "effect";
|
||||
import { Cause, type Layer, Logger } from "effect";
|
||||
|
||||
const render = (message: unknown): string => {
|
||||
if (typeof message === "string") return message;
|
||||
if (Array.isArray(message)) return message.map(render).join(" ");
|
||||
return typeof message === "object" ? JSON.stringify(message) : String(message);
|
||||
return typeof message === "object"
|
||||
? JSON.stringify(message)
|
||||
: String(message);
|
||||
};
|
||||
|
||||
/** Replace the default logger with the runner-journal format. */
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
// route init must read the last pathname segment — the hash is only a standalone-tab
|
||||
// fallback. Navigation posts `pf-ui:navigate` so the console mirrors the route into its
|
||||
// own URL (replace: true; the iframe src stays pinned — no reload loop).
|
||||
import { useEffect, useState, type ReactNode } from "react";
|
||||
|
||||
import { Option, Schema } from "effect";
|
||||
import { AsyncResult, Atom } from "effect/unstable/reactivity";
|
||||
import { type ReactNode, useEffect, useState } from "react";
|
||||
|
||||
/** `/plugin-ui/<id>` when served through the console proxy, "" in dev/standalone. */
|
||||
export const resolvePluginBase = (): string => {
|
||||
@@ -110,9 +111,7 @@ export interface ResultGateProps<A, E> {
|
||||
* The one loading/error/success convention for plugin pages. Keeps showing the last
|
||||
* value while a refresh is in flight (no skeleton flash on invalidation).
|
||||
*/
|
||||
export const ResultGate = <A, E>(
|
||||
props: ResultGateProps<A, E>,
|
||||
): ReactNode => {
|
||||
export const ResultGate = <A, E>(props: ResultGateProps<A, E>): ReactNode => {
|
||||
const { result } = props;
|
||||
if (AsyncResult.isSuccess(result)) return props.children(result.value);
|
||||
if (AsyncResult.isFailure(result)) {
|
||||
|
||||
@@ -39,7 +39,9 @@ export interface ProviderClientService {
|
||||
* Remove every entry this provider owns **and release its store claim** (the explicit-uninstall
|
||||
* path). Releasing is what brings the host's built-in scanner back.
|
||||
*/
|
||||
readonly remove: (providerId: string) => Effect.Effect<void, HostRequestError>;
|
||||
readonly remove: (
|
||||
providerId: string,
|
||||
) => Effect.Effect<void, HostRequestError>;
|
||||
}
|
||||
|
||||
export class ProviderClient extends Context.Service<
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
// plugin fiber (running scoped finalizers: UI deregistration, watcher close, cache flush)
|
||||
// and bounds the whole teardown with `shutdownGraceMs` so `main` always resolves.
|
||||
import {
|
||||
connect,
|
||||
definePlugin,
|
||||
type PluginDef,
|
||||
type Punktfunk,
|
||||
connect,
|
||||
} from "@punktfunk/host";
|
||||
import {
|
||||
Cause,
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
Fiber,
|
||||
Layer,
|
||||
ManagedRuntime,
|
||||
Scope,
|
||||
type Scope,
|
||||
} from "effect";
|
||||
import {
|
||||
type HostClient,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// beta.99), so the status feed is a raw HttpRouter route beside the HttpApi contract —
|
||||
// same wire shape the first-generation plugins used (`event: <name>` frames + comment
|
||||
// pings), which is already proven through the console's reverse proxy.
|
||||
import { Effect, Layer, Schedule, Stream } from "effect";
|
||||
import { Effect, type Layer, Schedule, Stream } from "effect";
|
||||
import { HttpRouter, HttpServerResponse } from "effect/unstable/http";
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import * as fs from "node:fs";
|
||||
import {
|
||||
Cause,
|
||||
type Duration,
|
||||
Effect,
|
||||
Exit,
|
||||
@@ -40,7 +39,11 @@ export interface LastSync {
|
||||
}
|
||||
|
||||
export type SyncOutcome<Report> =
|
||||
| { readonly _tag: "Applied"; readonly report: Report; readonly count: number }
|
||||
| {
|
||||
readonly _tag: "Applied";
|
||||
readonly report: Report;
|
||||
readonly count: number;
|
||||
}
|
||||
| { readonly _tag: "Unchanged"; readonly report: Report }
|
||||
| { readonly _tag: "AlreadyRunning" };
|
||||
|
||||
@@ -278,8 +281,6 @@ export const makeSyncEngine = <
|
||||
status,
|
||||
changes: Stream.fromPubSub(hub),
|
||||
start: safeSync("startup").pipe(Effect.andThen(startLoops)),
|
||||
reconfigure: startLoops.pipe(
|
||||
Effect.andThen(safeSync("config-change")),
|
||||
),
|
||||
reconfigure: startLoops.pipe(Effect.andThen(safeSync("config-change"))),
|
||||
} satisfies SyncEngine<Report>;
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// register/renew/deregister through Scope. Validated end-to-end by the phase-0 spike:
|
||||
// core-only env layers, no platform package, SPA fallthrough preserved.
|
||||
import { type PluginUiHandle, servePluginUi } from "@punktfunk/host";
|
||||
import { Effect, FileSystem, Layer, Path, Schema, Scope } from "effect";
|
||||
import { Effect, FileSystem, Layer, Path, Schema, type Scope } from "effect";
|
||||
import { Etag, HttpPlatform, HttpRouter } from "effect/unstable/http";
|
||||
import type { ConfigService } from "./config.js";
|
||||
import { UiServeError } from "./errors.js";
|
||||
@@ -176,7 +176,9 @@ export const serveUi = (
|
||||
Effect.promise(() => dispose()).pipe(Effect.ignore),
|
||||
);
|
||||
|
||||
const serveConfig = opts.config ? makeConfigHandler(opts.config) : undefined;
|
||||
const serveConfig = opts.config
|
||||
? makeConfigHandler(opts.config)
|
||||
: undefined;
|
||||
|
||||
const fetch = async (req: Request): Promise<Response | undefined> => {
|
||||
const url = new URL(req.url);
|
||||
@@ -203,9 +205,7 @@ export const serveUi = (
|
||||
...(opts.staticDir !== undefined
|
||||
? { staticDir: opts.staticDir }
|
||||
: {}),
|
||||
...(opts.category !== undefined
|
||||
? { category: opts.category }
|
||||
: {}),
|
||||
...(opts.category !== undefined ? { category: opts.category } : {}),
|
||||
fetch,
|
||||
}),
|
||||
catch: (cause) => new UiServeError({ cause }),
|
||||
|
||||
@@ -71,10 +71,7 @@ describe("ConfigService", () => {
|
||||
test("saveRaw persists the RAW shape verbatim (no defaults baked in)", async () => {
|
||||
await withService((svc) => svc.saveRaw({ roots: ["/roms"] }));
|
||||
const onDisk = JSON.parse(
|
||||
fs.readFileSync(
|
||||
path.join(pluginStateDir(PLUGIN), "config.json"),
|
||||
"utf8",
|
||||
),
|
||||
fs.readFileSync(path.join(pluginStateDir(PLUGIN), "config.json"), "utf8"),
|
||||
);
|
||||
expect(onDisk).toEqual({ roots: ["/roms"] }); // no sync block materialized
|
||||
const loaded = await withService((svc) => svc.load);
|
||||
|
||||
@@ -25,7 +25,10 @@ const ScannerConfig = Schema.Struct({
|
||||
}),
|
||||
),
|
||||
root: Schema.optionalKey(
|
||||
Schema.String.annotate({ title: "Launcher root", description: "Absolute path." }),
|
||||
Schema.String.annotate({
|
||||
title: "Launcher root",
|
||||
description: "Absolute path.",
|
||||
}),
|
||||
),
|
||||
extraRoots: Schema.Array(Schema.String)
|
||||
.annotate({ title: "Extra roots" })
|
||||
@@ -36,7 +39,10 @@ const ScannerConfig = Schema.Struct({
|
||||
),
|
||||
),
|
||||
launchers: Schema.Struct({
|
||||
bigpicture: Schema.Boolean.annotate({ title: "Big Picture", default: true }),
|
||||
bigpicture: Schema.Boolean.annotate({
|
||||
title: "Big Picture",
|
||||
default: true,
|
||||
}),
|
||||
desktop: Schema.Boolean.annotate({ title: "Desktop", default: false }),
|
||||
}).pipe(
|
||||
Schema.withDecodingDefaultKey(
|
||||
@@ -80,7 +86,10 @@ describe("S2 — JSON Schema derivation for __config", () => {
|
||||
// A nested object stays nested — the form renders a fieldset, not a JSON blob.
|
||||
expect(p.launchers).toMatchObject({
|
||||
type: "object",
|
||||
properties: { bigpicture: { type: "boolean" }, desktop: { type: "boolean" } },
|
||||
properties: {
|
||||
bigpicture: { type: "boolean" },
|
||||
desktop: { type: "boolean" },
|
||||
},
|
||||
});
|
||||
// A literal union derives a clean enum — prefer it over a union of strings.
|
||||
expect(p.artSource).toMatchObject({
|
||||
@@ -92,7 +101,9 @@ describe("S2 — JSON Schema derivation for __config", () => {
|
||||
test("annotations pass through — they are the ONLY source of labels and defaults", () => {
|
||||
const p = props();
|
||||
expect(p.enabled.title).toBe("Enable scanning");
|
||||
expect(p.enabled.description).toBe("Whether this source contributes titles.");
|
||||
expect(p.enabled.description).toBe(
|
||||
"Whether this source contributes titles.",
|
||||
);
|
||||
// The derivation does NOT infer `default` from withDecodingDefaultKey, so an un-annotated
|
||||
// field shows the form no placeholder at all. Annotate every field.
|
||||
expect(p.enabled.default).toBe(true);
|
||||
@@ -120,13 +131,13 @@ describe("S2 — JSON Schema derivation for __config", () => {
|
||||
expect(props().pollMinutes).toMatchObject({ type: "integer" });
|
||||
// The trap, pinned: Schema.Number's ENCODED form admits "NaN"/"Infinity"/"-Infinity", so it
|
||||
// derives a four-way anyOf that no number input can render. Use Finite or Int.
|
||||
const bad = deriveConfigJsonSchema(
|
||||
Schema.Struct({ n: Schema.Number }),
|
||||
) as { schema: { properties: { n: { anyOf?: unknown[] } } } };
|
||||
const bad = deriveConfigJsonSchema(Schema.Struct({ n: Schema.Number })) as {
|
||||
schema: { properties: { n: { anyOf?: unknown[] } } };
|
||||
};
|
||||
expect(Array.isArray(bad.schema.properties.n.anyOf)).toBe(true);
|
||||
const ok = deriveConfigJsonSchema(
|
||||
Schema.Struct({ n: Schema.Finite }),
|
||||
) as { schema: { properties: { n: { type?: string } } } };
|
||||
const ok = deriveConfigJsonSchema(Schema.Struct({ n: Schema.Finite })) as {
|
||||
schema: { properties: { n: { type?: string } } };
|
||||
};
|
||||
expect(ok.schema.properties.n.type).toBe("number");
|
||||
});
|
||||
|
||||
@@ -141,7 +152,10 @@ describe("S2 — JSON Schema derivation for __config", () => {
|
||||
|
||||
describe("__config wire contract", () => {
|
||||
const withService = async <A>(
|
||||
use: (handler: (req: Request) => Promise<Response>, file: string) => Promise<A>,
|
||||
use: (
|
||||
handler: (req: Request) => Promise<Response>,
|
||||
file: string,
|
||||
) => Promise<A>,
|
||||
): Promise<A> => {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pf-kit-cfg-"));
|
||||
const prev = process.env.PUNKTFUNK_CONFIG_DIR;
|
||||
@@ -150,7 +164,9 @@ describe("__config wire contract", () => {
|
||||
const service = await Effect.runPromise(
|
||||
makeConfigService({ schema: ScannerConfig }).pipe(
|
||||
Effect.provide(
|
||||
Layer.mergeAll(pluginInfoLayer({ name: "steam", version: "0.1.0" })),
|
||||
Layer.mergeAll(
|
||||
pluginInfoLayer({ name: "steam", version: "0.1.0" }),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -40,7 +40,8 @@ const pluginEntry = (over: Partial<ProviderEntry> = {}): ProviderEntry =>
|
||||
art: {
|
||||
portrait: "file:///home/u/.steam/appcache/librarycache/440/a/p.jpg",
|
||||
hero: "https://cdn.cloudflare.steamstatic.com/steam/apps/440/library_hero.jpg",
|
||||
header: "https://cdn.cloudflare.steamstatic.com/steam/apps/440/header.jpg",
|
||||
header:
|
||||
"https://cdn.cloudflare.steamstatic.com/steam/apps/440/header.jpg",
|
||||
},
|
||||
platform: "PC",
|
||||
...over,
|
||||
|
||||
@@ -5,23 +5,24 @@
|
||||
// and launches nothing. Where a Rust test exists, its assertions are carried over verbatim — the
|
||||
// per-plugin parity harness (design M5) then checks the whole pipeline against a live host, but
|
||||
// these catch a drift long before that.
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
import { Database } from "bun:sqlite";
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import {
|
||||
confinedJoin,
|
||||
crc32,
|
||||
fileUrl,
|
||||
findGridArtFile,
|
||||
findLocalArtFile,
|
||||
fileUrl,
|
||||
gridFilenames,
|
||||
isSteamTool,
|
||||
withReadOnlyDb,
|
||||
openReadOnly,
|
||||
parseAppManifest,
|
||||
parseRegQuery,
|
||||
parseRegSubKeys,
|
||||
parseShortcuts,
|
||||
readTextCapped,
|
||||
shortcutAppId,
|
||||
@@ -29,6 +30,7 @@ import {
|
||||
steamCdnUrl,
|
||||
vdfPaths,
|
||||
vdfValue,
|
||||
withReadOnlyDb,
|
||||
} from "../src/library/parsers/index.js";
|
||||
|
||||
const tmp = (name: string): string => {
|
||||
@@ -85,7 +87,9 @@ describe("text VDF / ACF", () => {
|
||||
});
|
||||
|
||||
test("isSteamTool keeps runtimes out of a game library", () => {
|
||||
expect(isSteamTool(228980, "Steamworks Common Redistributables")).toBe(true);
|
||||
expect(isSteamTool(228980, "Steamworks Common Redistributables")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(isSteamTool(1628350, "Steam Linux Runtime 3.0 (sniper)")).toBe(true);
|
||||
expect(isSteamTool(999, "Proton 9.0")).toBe(true);
|
||||
expect(isSteamTool(999, "SteamVR")).toBe(true);
|
||||
@@ -109,7 +113,12 @@ describe("binary shortcuts.vdf", () => {
|
||||
parts.push(0);
|
||||
};
|
||||
const i32 = (v: number) => {
|
||||
parts.push(v & 0xff, (v >>> 8) & 0xff, (v >>> 16) & 0xff, (v >>> 24) & 0xff);
|
||||
parts.push(
|
||||
v & 0xff,
|
||||
(v >>> 8) & 0xff,
|
||||
(v >>> 16) & 0xff,
|
||||
(v >>> 24) & 0xff,
|
||||
);
|
||||
};
|
||||
parts.push(0x00);
|
||||
cstr("shortcuts");
|
||||
@@ -205,9 +214,13 @@ describe("path confinement", () => {
|
||||
path.join(base, "bin", "game.exe"),
|
||||
);
|
||||
// The three shapes a crafted goggame-*.info would use to point elsewhere.
|
||||
expect(confinedJoin(base, "../../windows/system32/cmd.exe")).toBeUndefined();
|
||||
expect(
|
||||
confinedJoin(base, "../../windows/system32/cmd.exe"),
|
||||
).toBeUndefined();
|
||||
expect(confinedJoin(base, "/etc/passwd")).toBeUndefined();
|
||||
expect(confinedJoin(base, "C:\\Windows\\system32\\cmd.exe")).toBeUndefined();
|
||||
expect(
|
||||
confinedJoin(base, "C:\\Windows\\system32\\cmd.exe"),
|
||||
).toBeUndefined();
|
||||
expect(confinedJoin(base, "")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -237,8 +250,14 @@ describe("art locations", () => {
|
||||
|
||||
test("grid filenames follow Steam's per-kind naming", () => {
|
||||
expect(gridFilenames(570, "portrait")).toEqual(["570p.png", "570p.jpg"]);
|
||||
expect(gridFilenames(570, "hero")).toEqual(["570_hero.png", "570_hero.jpg"]);
|
||||
expect(gridFilenames(570, "logo")).toEqual(["570_logo.png", "570_logo.jpg"]);
|
||||
expect(gridFilenames(570, "hero")).toEqual([
|
||||
"570_hero.png",
|
||||
"570_hero.jpg",
|
||||
]);
|
||||
expect(gridFilenames(570, "logo")).toEqual([
|
||||
"570_logo.png",
|
||||
"570_logo.jpg",
|
||||
]);
|
||||
expect(gridFilenames(570, "header")).toEqual(["570.png", "570.jpg"]);
|
||||
});
|
||||
|
||||
@@ -307,8 +326,12 @@ describe("openReadOnly", () => {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pf-kit-sqlite-"));
|
||||
const file = path.join(dir, "pga.db");
|
||||
const seed = new Database(file);
|
||||
seed.run("CREATE TABLE games (id INTEGER PRIMARY KEY, name TEXT, installed INT)");
|
||||
seed.run("INSERT INTO games (id, name, installed) VALUES (1, 'Ubisoft Connect', 1)");
|
||||
seed.run(
|
||||
"CREATE TABLE games (id INTEGER PRIMARY KEY, name TEXT, installed INT)",
|
||||
);
|
||||
seed.run(
|
||||
"INSERT INTO games (id, name, installed) VALUES (1, 'Ubisoft Connect', 1)",
|
||||
);
|
||||
seed.close();
|
||||
try {
|
||||
return use(file);
|
||||
@@ -321,9 +344,9 @@ describe("openReadOnly", () => {
|
||||
withDb((file) => {
|
||||
const db = openReadOnly(file);
|
||||
expect(db).toBeDefined();
|
||||
expect(db?.query("SELECT id, name FROM games WHERE installed = 1")).toEqual([
|
||||
{ id: 1, name: "Ubisoft Connect" },
|
||||
]);
|
||||
expect(
|
||||
db?.query("SELECT id, name FROM games WHERE installed = 1"),
|
||||
).toEqual([{ id: 1, name: "Ubisoft Connect" }]);
|
||||
db?.close();
|
||||
});
|
||||
});
|
||||
@@ -337,7 +360,9 @@ describe("openReadOnly", () => {
|
||||
seed.run("INSERT INTO games (id) VALUES (7)");
|
||||
seed.close();
|
||||
try {
|
||||
expect(openReadOnly(file)?.query("SELECT id FROM games")).toEqual([{ id: 7 }]);
|
||||
expect(openReadOnly(file)?.query("SELECT id FROM games")).toEqual([
|
||||
{ id: 7 },
|
||||
]);
|
||||
} finally {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
@@ -345,16 +370,20 @@ describe("openReadOnly", () => {
|
||||
|
||||
test("withReadOnlyDb reads, then closes", () => {
|
||||
withDb((file) => {
|
||||
expect(withReadOnlyDb(file, (h) => h.query("SELECT name FROM games"))).toEqual([
|
||||
{ name: "Ubisoft Connect" },
|
||||
]);
|
||||
expect(
|
||||
withReadOnlyDb(file, (h) => h.query("SELECT name FROM games")),
|
||||
).toEqual([{ name: "Ubisoft Connect" }]);
|
||||
});
|
||||
});
|
||||
|
||||
// The "not installed" contract — an absent file is `undefined`, never a throw.
|
||||
test("absent file is undefined, not an error", () => {
|
||||
expect(openReadOnly(path.join(os.tmpdir(), "pf-kit-nope", "pga.db"))).toBeUndefined();
|
||||
expect(withReadOnlyDb(path.join(os.tmpdir(), "pf-kit-nope", "pga.db"), () => 1)).toBeUndefined();
|
||||
expect(
|
||||
openReadOnly(path.join(os.tmpdir(), "pf-kit-nope", "pga.db")),
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
withReadOnlyDb(path.join(os.tmpdir(), "pf-kit-nope", "pga.db"), () => 1),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
// Schema drift degrades to no rows rather than taking the plugin down.
|
||||
@@ -366,3 +395,68 @@ describe("openReadOnly", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Subkey enumeration, against the output reg.exe ACTUALLY prints.
|
||||
//
|
||||
// This had no coverage and was broken end to end: it matched lines against the abbreviated
|
||||
// `HKLM\…` prefix it was handed, but reg.exe echoes `HKEY_LOCAL_MACHINE\…`. Nothing ever matched,
|
||||
// so it returned [] on every machine, and the GOG plugin — its only consumer — reported "no games
|
||||
// installed" rather than failing. Caught on hardware by the parity gate: the host's built-in
|
||||
// scanner found IRON NEST, the plugin found nothing.
|
||||
//
|
||||
// The fixture is the verbatim output from .173 (a blank line, then one subkey row).
|
||||
describe("parseRegSubKeys", () => {
|
||||
const KEY = "HKLM\\SOFTWARE\\WOW6432Node\\GOG.com\\Games";
|
||||
|
||||
test("returns subkey NAMES from real reg.exe output", () => {
|
||||
const stdout = [
|
||||
"",
|
||||
"HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\GOG.com\\Games\\2013434102",
|
||||
"",
|
||||
].join("\r\n");
|
||||
// The name is the GOG product id, and the consumer composes `${KEY}\\${name}`.
|
||||
expect(parseRegSubKeys(stdout, KEY)).toEqual(["2013434102"]);
|
||||
});
|
||||
|
||||
test("several subkeys, in order", () => {
|
||||
const base = "HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\GOG.com\\Games";
|
||||
const stdout = ["", `${base}\\1207658930`, `${base}\\2013434102`].join(
|
||||
"\r\n",
|
||||
);
|
||||
expect(parseRegSubKeys(stdout, KEY)).toEqual(["1207658930", "2013434102"]);
|
||||
});
|
||||
|
||||
// reg.exe /s output nests deeper; only immediate children are subkeys of this key.
|
||||
test("ignores grandchildren", () => {
|
||||
const base = "HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\GOG.com\\Games";
|
||||
const stdout = [
|
||||
"",
|
||||
`${base}\\2013434102`,
|
||||
`${base}\\2013434102\\tasks`,
|
||||
].join("\r\n");
|
||||
expect(parseRegSubKeys(stdout, KEY)).toEqual(["2013434102"]);
|
||||
});
|
||||
|
||||
// The queried key itself is echoed as a header when it has values; it is not its own subkey.
|
||||
test("does not return the queried key itself", () => {
|
||||
const stdout = [
|
||||
"",
|
||||
"HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\GOG.com\\Games",
|
||||
"",
|
||||
].join("\r\n");
|
||||
expect(parseRegSubKeys(stdout, KEY)).toEqual([]);
|
||||
});
|
||||
|
||||
test("case-insensitive on the hive and path", () => {
|
||||
const stdout =
|
||||
"hkey_local_machine\\software\\wow6432node\\gog.com\\games\\42";
|
||||
expect(parseRegSubKeys(stdout, KEY)).toEqual(["42"]);
|
||||
});
|
||||
|
||||
test("no subkeys is empty, not a throw", () => {
|
||||
expect(parseRegSubKeys("", KEY)).toEqual([]);
|
||||
expect(
|
||||
parseRegSubKeys("ERROR: The system was unable to find...", KEY),
|
||||
).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -45,9 +45,7 @@ describe("spike 2: client prefix through the console proxy", () => {
|
||||
const captured: Array<string> = [];
|
||||
class Api extends AtomHttpApi.Service<Api>()("SpikeApiPrepend", {
|
||||
api,
|
||||
httpClient: Layer.succeed(HttpClient.HttpClient)(
|
||||
captureClient(captured),
|
||||
),
|
||||
httpClient: Layer.succeed(HttpClient.HttpClient)(captureClient(captured)),
|
||||
transformClient: HttpClient.mapRequest(
|
||||
HttpClientRequest.prependUrl(PREFIX),
|
||||
),
|
||||
@@ -66,9 +64,7 @@ describe("spike 2: client prefix through the console proxy", () => {
|
||||
const captured: Array<string> = [];
|
||||
class Api extends AtomHttpApi.Service<Api>()("SpikeApiBaseUrl", {
|
||||
api,
|
||||
httpClient: Layer.succeed(HttpClient.HttpClient)(
|
||||
captureClient(captured),
|
||||
),
|
||||
httpClient: Layer.succeed(HttpClient.HttpClient)(captureClient(captured)),
|
||||
baseUrl: PREFIX,
|
||||
}) {}
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
// 3. The real servePluginUi server (loopback, per-boot bearer secret, __health) proxies
|
||||
// into the HttpApi handler end-to-end.
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import type { Punktfunk } from "@punktfunk/host";
|
||||
import { servePluginUi } from "@punktfunk/host";
|
||||
import { Effect, Layer, Schema } from "effect";
|
||||
import * as FileSystem from "effect/FileSystem";
|
||||
import * as Path from "effect/Path";
|
||||
@@ -19,8 +21,6 @@ import {
|
||||
HttpApiEndpoint,
|
||||
HttpApiGroup,
|
||||
} from "effect/unstable/httpapi";
|
||||
import { servePluginUi } from "@punktfunk/host";
|
||||
import type { Punktfunk } from "@punktfunk/host";
|
||||
|
||||
const Pong = Schema.Struct({ ok: Schema.Boolean, source: Schema.String });
|
||||
const EchoIn = Schema.Struct({ msg: Schema.String });
|
||||
@@ -90,8 +90,11 @@ describe("spike 1: HttpApi via toWebHandler on Bun", () => {
|
||||
|
||||
test("end-to-end behind servePluginUi (loopback + bearer secret)", async () => {
|
||||
const { handler, dispose } = HttpRouter.toWebHandler(appLayer);
|
||||
const registrations: Array<{ method: string; path: string; body: unknown }> =
|
||||
[];
|
||||
const registrations: Array<{
|
||||
method: string;
|
||||
path: string;
|
||||
body: unknown;
|
||||
}> = [];
|
||||
// servePluginUi only touches pf.request — a recording stub is a faithful host.
|
||||
const pf = {
|
||||
request: async (method: string, path: string, body?: unknown) => {
|
||||
@@ -116,15 +119,18 @@ describe("spike 1: HttpApi via toWebHandler on Bun", () => {
|
||||
(r) => r.method === "PUT" && r.path === "/plugins/spike",
|
||||
);
|
||||
expect(reg).toBeDefined();
|
||||
const secret = (reg?.body as { ui: { secret: string } }).ui.secret;
|
||||
// Not `reg?.body`: the optional chain undoes the assertion above — if `reg` were
|
||||
// undefined the `.ui` access would throw a TypeError instead of failing this test
|
||||
// readably. The `expect` is what guarantees it, so assert it to the type system too.
|
||||
if (!reg) throw new Error("registration not found");
|
||||
const secret = (reg.body as { ui: { secret: string } }).ui.secret;
|
||||
expect(secret.length).toBeGreaterThanOrEqual(16);
|
||||
const auth = { authorization: `Bearer ${secret}` };
|
||||
|
||||
// Health endpoint is served by servePluginUi itself.
|
||||
const health = await fetch(
|
||||
`http://127.0.0.1:${ui.port}/__health`,
|
||||
{ headers: auth },
|
||||
);
|
||||
const health = await fetch(`http://127.0.0.1:${ui.port}/__health`, {
|
||||
headers: auth,
|
||||
});
|
||||
expect(health.status).toBe(200);
|
||||
|
||||
// HttpApi endpoint through the real server.
|
||||
|
||||
@@ -44,7 +44,9 @@ describe("sseRoute (live, PubSub-backed)", () => {
|
||||
const { handler, dispose } = HttpRouter.toWebHandler(
|
||||
Layer.provide(routes, httpApiEnv),
|
||||
);
|
||||
const res = yield* Effect.promise(() => handler(new Request("http://127.0.0.1/api/events")));
|
||||
const res = yield* Effect.promise(() =>
|
||||
handler(new Request("http://127.0.0.1/api/events")),
|
||||
);
|
||||
expect(res.status).toBe(200);
|
||||
// Publish only once the response is open — the real engine's pattern.
|
||||
setTimeout(() => {
|
||||
@@ -68,7 +70,9 @@ describe("sseRoute (live, PubSub-backed)", () => {
|
||||
const { handler, dispose } = HttpRouter.toWebHandler(
|
||||
Layer.provide(routes, httpApiEnv),
|
||||
);
|
||||
const res = yield* Effect.promise(() => handler(new Request("http://127.0.0.1/api/events")));
|
||||
const res = yield* Effect.promise(() =>
|
||||
handler(new Request("http://127.0.0.1/api/events")),
|
||||
);
|
||||
const body = yield* Effect.promise(() => readSome(res, 4000));
|
||||
yield* Effect.promise(() => dispose());
|
||||
return body;
|
||||
|
||||
@@ -18,13 +18,9 @@ describe("sseRoute", () => {
|
||||
Layer.provide(routes, httpApiEnv),
|
||||
);
|
||||
try {
|
||||
const res = await handler(
|
||||
new Request("http://127.0.0.1/api/events"),
|
||||
);
|
||||
const res = await handler(new Request("http://127.0.0.1/api/events"));
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get("content-type")).toContain(
|
||||
"text/event-stream",
|
||||
);
|
||||
expect(res.headers.get("content-type")).toContain("text/event-stream");
|
||||
const text = await res.text();
|
||||
expect(text).toContain('event: status\ndata: {"tick":0}\n\n');
|
||||
expect(text).toContain('event: status\ndata: {"tick":2}\n\n');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// SyncEngine semantics: fingerprint skip, single-flight coalescing, status feed.
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { Duration, Effect, Fiber, Ref, Scope, Stream } from "effect";
|
||||
import { Duration, Effect, Fiber, Ref, type Scope, Stream } from "effect";
|
||||
import {
|
||||
type LastSync,
|
||||
makeSyncEngine,
|
||||
@@ -19,33 +19,31 @@ const harness = (opts?: {
|
||||
const applied = yield* Ref.make(0);
|
||||
const last = yield* Ref.make<LastSync | undefined>(undefined);
|
||||
const entries = opts?.entries ?? (() => ["a", "b"]);
|
||||
const engine = yield* makeSyncEngine<Report, ReadonlyArray<string>, never>(
|
||||
{
|
||||
compute: () =>
|
||||
Effect.suspend(() => {
|
||||
const e = entries();
|
||||
return Effect.succeed({
|
||||
entries: e,
|
||||
report: { included: e.length },
|
||||
});
|
||||
}).pipe(
|
||||
opts?.computeDelayMs
|
||||
? Effect.delay(Duration.millis(opts.computeDelayMs))
|
||||
: (x) => x,
|
||||
),
|
||||
apply: () => Ref.update(applied, (n) => n + 1),
|
||||
lastSync: {
|
||||
get: Ref.get(last),
|
||||
set: (l) => Ref.set(last, l),
|
||||
},
|
||||
settings: Effect.succeed({
|
||||
pollInterval: Duration.minutes(60),
|
||||
watch: false,
|
||||
debounce: Duration.millis(10),
|
||||
watchDirs: [],
|
||||
}),
|
||||
const engine = yield* makeSyncEngine<Report, ReadonlyArray<string>, never>({
|
||||
compute: () =>
|
||||
Effect.suspend(() => {
|
||||
const e = entries();
|
||||
return Effect.succeed({
|
||||
entries: e,
|
||||
report: { included: e.length },
|
||||
});
|
||||
}).pipe(
|
||||
opts?.computeDelayMs
|
||||
? Effect.delay(Duration.millis(opts.computeDelayMs))
|
||||
: (x) => x,
|
||||
),
|
||||
apply: () => Ref.update(applied, (n) => n + 1),
|
||||
lastSync: {
|
||||
get: Ref.get(last),
|
||||
set: (l) => Ref.set(last, l),
|
||||
},
|
||||
);
|
||||
settings: Effect.succeed({
|
||||
pollInterval: Duration.minutes(60),
|
||||
watch: false,
|
||||
debounce: Duration.millis(10),
|
||||
watchDirs: [],
|
||||
}),
|
||||
});
|
||||
return { engine, applied, last };
|
||||
});
|
||||
|
||||
|
||||
@@ -45,6 +45,17 @@ if (Test-Path $rustup) {
|
||||
# signed DLLs in users' installs. The pins below were captured 2026-07-10 from the then-current
|
||||
# n7.1 lgpl-shared build. When BtbN re-rolls `latest`, this fetch FAILS CLOSED (hash mismatch) —
|
||||
# that is intentional: re-download, re-verify the new archive, and update the two pins here.
|
||||
#
|
||||
# STILL n7.1 AFTER THE 2026-08-08 ffmpeg-next 8 -> 9 BUMP, on purpose. A crate major is a CEILING,
|
||||
# not a target (ffmpeg-sys-next 9 spans libavcodec 56..63), so 7.1 keeps compiling; and Windows has
|
||||
# no exposure to the soname break that forced the bump, because these DLLs are BUNDLED into the
|
||||
# signed installer/MSIX rather than resolved from a system that can upgrade underneath them. BtbN
|
||||
# publishes no FFmpeg 9 build at all right now (`latest` carries n7.1 and n8.1 only), so matching
|
||||
# Arch is not even available. Moving this pin would swap the DLLs inside a code-signed installer and
|
||||
# re-qualify AMF/QSV encode on real Intel/AMD hardware, which is its own change with its own on-glass
|
||||
# pass — not a side effect of a Cargo bump. ⚠ One consequence to keep in mind while it stays here:
|
||||
# 7.1's `AVD3D11VADeviceContext` is two UINTs shorter than 8/9's, which is why the mirror in
|
||||
# crates/pf-encode/src/enc/windows/ffmpeg_win.rs deliberately stops at the common prefix.
|
||||
# Refresh a pin: (Get-FileHash .\ffmpeg-<tag>.zip -Algorithm SHA256).Hash
|
||||
function Get-BtbnFfmpeg {
|
||||
param([string]$Dir, [string]$ZipTag, [string]$Sha) # ZipTag: 'win64' (x64); BtbN also publishes 'winarm64'
|
||||
|
||||
Reference in New Issue
Block a user