From 80b6abdb323ab815c3ff69755f535fb7907a0dfd Mon Sep 17 00:00:00 2001 From: Harry Date: Thu, 8 Oct 2026 13:51:22 +0200 Subject: [PATCH] build: assemble pinned OBS 32.0.4 runtimes for Linux and Windows Linux builds libobs and an allowlist of official plugins from source in OBS's relocatable layout. Windows copies the official release byte-for-byte so the Game Capture hook keeps its signature. Adds a headless Wayland rig for PipeWire/portal capture tests on machines without a display. --- scripts/obs/assemble-windows.sh | 68 +++++++++++++++++++++++ scripts/obs/build-linux.sh | 97 +++++++++++++++++++++++++++++++++ scripts/obs/versions.env | 5 ++ scripts/rig/headless-wayland.sh | 94 ++++++++++++++++++++++++++++++++ 4 files changed, 264 insertions(+) create mode 100755 scripts/obs/assemble-windows.sh create mode 100755 scripts/obs/build-linux.sh create mode 100644 scripts/obs/versions.env create mode 100755 scripts/rig/headless-wayland.sh diff --git a/scripts/obs/assemble-windows.sh b/scripts/obs/assemble-windows.sh new file mode 100755 index 0000000..f02d035 --- /dev/null +++ b/scripts/obs/assemble-windows.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# Assemble the pinned OBS runtime for Windows x64 from the official release zip. +# +# Files are copied byte-for-byte from the OBS release so the signed Game +# Capture hook (graphics-hook64.dll, inject-helper, get-graphics-offsets) keeps +# its original signature. Runs on Linux or Git Bash; needs curl, unzip and +# sha256sum. +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=versions.env +source "${script_dir}/versions.env" + +cache_dir="${CLIPBIN_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/clipbin}" +zip_name="OBS-Studio-${OBS_VERSION}-Windows-x64.zip" +zip_path="${cache_dir}/${zip_name}" +extract_dir="${cache_dir}/obs-windows-${OBS_VERSION}" +bundle_dir="${CLIPBIN_OBS_RUNTIME:-${script_dir}/../../target/obs-runtime/windows-x86_64}" + +# Official plugins the recorder uses: Game/Window Capture, WASAPI audio, +# hardware encoders (AMF lives in obs-ffmpeg) and the FFmpeg muxer. +plugins=(win-capture win-wasapi obs-ffmpeg obs-nvenc obs-qsv11) +# OBS front-end, scripting and software-encoder files Clipbin does not use. +# Subdirectories of bin/64bit are Qt plugins and are skipped too. +excluded_bin='^(obs64\.exe|obs-frontend-api\.dll|obs-scripting\.dll|lua51\.dll|Qt6.*\.dll|datachannel\.dll|libx264-.*\.dll|.*\.pdb)$' + +mkdir -p "${cache_dir}" +if [[ ! -f "${zip_path}" ]]; then + curl -fL --retry 3 -o "${zip_path}.partial" \ + "https://github.com/obsproject/obs-studio/releases/download/${OBS_VERSION}/${zip_name}" + mv "${zip_path}.partial" "${zip_path}" +fi +echo "${OBS_WINDOWS_X64_SHA256} ${zip_path}" | sha256sum --check --quiet + +rm -rf "${extract_dir}" +unzip -q "${zip_path}" -d "${extract_dir}" + +# Keep the OBS install layout: OBS looks for obs-ffmpeg-mux.exe next to the +# running executable, so Clipbin's executable lives in bin/64bit. +rm -rf "${bundle_dir}" +mkdir -p "${bundle_dir}/bin/64bit" "${bundle_dir}/obs-plugins/64bit" "${bundle_dir}/data/obs-plugins" +for file in "${extract_dir}"/bin/64bit/*; do + name="$(basename "${file}")" + if [[ -f "${file}" && ! "${name}" =~ ${excluded_bin} ]]; then + cp -a "${file}" "${bundle_dir}/bin/64bit/" + fi +done +cp -a "${extract_dir}/data/libobs" "${bundle_dir}/data/libobs" +for plugin in "${plugins[@]}"; do + cp -a "${extract_dir}/obs-plugins/64bit/${plugin}.dll" "${bundle_dir}/obs-plugins/64bit/" + if [[ -d "${extract_dir}/data/obs-plugins/${plugin}" ]]; then + cp -a "${extract_dir}/data/obs-plugins/${plugin}" "${bundle_dir}/data/obs-plugins/" + fi +done +find "${bundle_dir}" -name '*.pdb' -delete + +# Record the signed capture-hook files so a Windows check can verify them +# against Get-AuthenticodeSignature before packaging. +(cd "${bundle_dir}" && sha256sum data/obs-plugins/win-capture/*.exe data/obs-plugins/win-capture/*.dll) \ + > "${bundle_dir}/SIGNED-HOOKS.sha256" + +cat > "${bundle_dir}/VERSION" <&2 + exit 1 +fi + +cmake -S "${source_dir}" -B "${build_dir}" -G Ninja \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DCMAKE_INSTALL_PREFIX="${stage_dir}" \ + -DENABLE_RELOCATABLE=ON \ + -DENABLE_PORTABLE_CONFIG=ON \ + -DENABLE_FRONTEND=OFF \ + -DENABLE_UI=OFF \ + -DENABLE_BROWSER=OFF \ + -DENABLE_WEBSOCKET=OFF \ + -DENABLE_WEBRTC=OFF \ + -DENABLE_SCRIPTING=OFF \ + -DENABLE_VLC=OFF \ + -DENABLE_AJA=OFF \ + -DENABLE_DECKLINK=OFF \ + -DENABLE_VST=OFF \ + -DENABLE_QSV11=OFF \ + -DENABLE_LIBFDK=OFF \ + -DENABLE_JACK=OFF \ + -DENABLE_SNDIO=OFF \ + -DENABLE_OSS=OFF \ + -DENABLE_V4L2=OFF \ + -DENABLE_VIRTUALCAM=OFF \ + -DENABLE_NEW_MPEGTS_OUTPUT=OFF \ + -DENABLE_PIPEWIRE=ON \ + -DENABLE_WAYLAND=ON \ + -DENABLE_PULSEAUDIO=ON \ + -DENABLE_NVENC=ON \ + -DENABLE_HEVC=ON \ + "${extra_cmake_args[@]}" +cmake --build "${build_dir}" +rm -rf "${stage_dir}" +cmake --install "${build_dir}" + +# The bundle keeps OBS's relocatable install layout: binaries in bin/ (OBS +# looks for obs-ffmpeg-mux next to the running executable), libraries in lib/, +# and plugins in lib/obs-plugins/ so their $ORIGIN/.. runpath resolves libobs. +rm -rf "${bundle_dir}" +mkdir -p "${bundle_dir}/bin" "${bundle_dir}/lib/obs-plugins" "${bundle_dir}/lib/pkgconfig" \ + "${bundle_dir}/share/obs/obs-plugins" +cp -a "${stage_dir}/bin/obs-ffmpeg-mux" "${bundle_dir}/bin/" +cp -a "${stage_dir}"/lib/libobs.so* "${stage_dir}"/lib/libobs-opengl.so* "${bundle_dir}/lib/" +cp -a "${stage_dir}/share/obs/libobs" "${bundle_dir}/share/obs/libobs" +for plugin in "${plugins[@]}"; do + cp -a "${stage_dir}/lib/obs-plugins/${plugin}.so" "${bundle_dir}/lib/obs-plugins/" + if [[ -d "${stage_dir}/share/obs/obs-plugins/${plugin}" ]]; then + cp -a "${stage_dir}/share/obs/obs-plugins/${plugin}" "${bundle_dir}/share/obs/obs-plugins/" + fi +done +# Lets the libobs crate link against this runtime instead of a system OBS. +sed -e 's|^prefix=.*|prefix=${pcfiledir}/../..|' \ + "${stage_dir}/lib/pkgconfig/libobs.pc" > "${bundle_dir}/lib/pkgconfig/libobs.pc" + +cat > "${bundle_dir}/VERSION" < [args...] +# +# Starts a private D-Bus session with PipeWire, WirePlumber, headless Sway and +# xdg-desktop-portal-wlr (auto-selecting the only output), plus a 60 FPS test +# pattern standing in for a game. Everything is torn down when the command exits. +# +# This rig exercises the PipeWire/portal path on a machine without a display. +# It does not replace validation on real hardware (Hyprland, X11, Windows). +set -euo pipefail + +if [[ $# -eq 0 ]]; then + echo "usage: $0 [args...]" >&2 + exit 2 +fi + +if [[ -z "${CLIPBIN_RIG_INNER:-}" ]]; then + exec env CLIPBIN_RIG_INNER=1 dbus-run-session -- "$0" "$@" +fi + +rig_dir="$(mktemp -d "${TMPDIR:-/tmp}/clipbin-rig.XXXXXX")" +pids=() +cleanup() { + for pid in "${pids[@]}"; do kill "${pid}" 2>/dev/null || true; done + wait 2>/dev/null || true + # The D-Bus-activated document portal mounts a FUSE filesystem here. + fusermount3 -u "${XDG_RUNTIME_DIR}/doc" 2>/dev/null || true + rm -rf "${rig_dir}" +} +trap cleanup EXIT + +export XDG_RUNTIME_DIR="${rig_dir}/runtime" +export XDG_CONFIG_HOME="${rig_dir}/config" +export XDG_CURRENT_DESKTOP=sway +export XDG_SESSION_TYPE=wayland +mkdir -p "${XDG_RUNTIME_DIR}" "${XDG_CONFIG_HOME}/xdg-desktop-portal-wlr" "${XDG_CONFIG_HOME}/sway" +chmod 700 "${XDG_RUNTIME_DIR}" + +width="${CLIPBIN_RIG_WIDTH:-1920}" +height="${CLIPBIN_RIG_HEIGHT:-1080}" + +cat > "${XDG_CONFIG_HOME}/sway/config" < "${XDG_CONFIG_HOME}/xdg-desktop-portal-wlr/config" <&2 + exit 1 +} + +pipewire > "${rig_dir}/pipewire.log" 2>&1 & pids+=($!) +wait_for pipewire '[[ -S "${XDG_RUNTIME_DIR}/pipewire-0" ]]' +wireplumber > "${rig_dir}/wireplumber.log" 2>&1 & pids+=($!) +pipewire-pulse > "${rig_dir}/pipewire-pulse.log" 2>&1 & pids+=($!) + +WLR_BACKENDS=headless WLR_LIBINPUT_NO_DEVICES=1 WLR_HEADLESS_OUTPUTS=1 \ + sway --config "${XDG_CONFIG_HOME}/sway/config" > "${rig_dir}/sway.log" 2>&1 & pids+=($!) +wait_for sway 'compgen -G "${XDG_RUNTIME_DIR}/wayland-*" > /dev/null' +WAYLAND_DISPLAY="$(basename "$(compgen -G "${XDG_RUNTIME_DIR}/wayland-*" | grep -v lock | head -1)")" +export WAYLAND_DISPLAY +SWAYSOCK="$(compgen -G "${XDG_RUNTIME_DIR}/sway-ipc.*" | head -1)" +export SWAYSOCK + +dbus-update-activation-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP XDG_RUNTIME_DIR XDG_CONFIG_HOME SWAYSOCK +/usr/lib/xdg-desktop-portal-wlr > "${rig_dir}/xdpw.log" 2>&1 & pids+=($!) +/usr/lib/xdg-desktop-portal -r > "${rig_dir}/xdp.log" 2>&1 & pids+=($!) + +# A moving 60 FPS picture with a tone, standing in for a game. +SDL_VIDEODRIVER=wayland ffplay -loglevel error -fs -window_title clipbin-test-game \ + -f lavfi "testsrc2=size=${width}x${height}:rate=60,format=yuv420p" \ + > "${rig_dir}/game.log" 2>&1 & pids+=($!) +sleep 1 + +echo "rig: ready (WAYLAND_DISPLAY=${WAYLAND_DISPLAY}, logs in ${rig_dir})" >&2 +status=0 +"$@" || status=$? +if [[ -n "${CLIPBIN_RIG_KEEP_LOGS:-}" ]]; then + cp -r "${rig_dir}" "${CLIPBIN_RIG_KEEP_LOGS}" +fi +exit "${status}"