Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions .github/workflows/qt-release-candidate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,14 @@ jobs:
mkdir -p build/release-artifacts
cpack --config build/qt-release/CPackConfig.cmake -C Release -G DEB -B build/release-artifacts
cmake --install build/qt-release --config Release --prefix "$PWD/build/AppDir/usr"
test -f build/AppDir/usr/bin/libopennow_streamer_ffi.so
test ! -e build/AppDir/usr/bin/opennow-streamer
deb=$(find build/release-artifacts -maxdepth 1 -type f -name '*.deb' -print -quit)
dpkg-deb --fsys-tarfile "$deb" | tar -tf - | grep -qx './usr/bin/libopennow_streamer_ffi.so'
if dpkg-deb --fsys-tarfile "$deb" | tar -tf - | grep -qx './usr/bin/opennow-streamer'; then
echo 'Standalone streamer must not be packaged' >&2
exit 1
fi
curl --fail --location --retry 3 \
"https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20251107-1/linuxdeploy-${LINUXDEPLOY_ARCH}.AppImage" \
--output "build/linuxdeploy-${LINUXDEPLOY_ARCH}.AppImage"
Expand Down Expand Up @@ -301,9 +309,9 @@ jobs:
$ErrorActionPreference = "Stop"
$pfx = Join-Path $env:RUNNER_TEMP "opennow-signing.pfx"
[IO.File]::WriteAllBytes($pfx, [Convert]::FromBase64String($env:WINDOWS_PFX_BASE64))
$names = @("OpenNOW.exe", "opennow-core.exe", "opennow-streamer.exe", "opennow-acceptance-verify.exe")
$names = @("OpenNOW.exe", "opennow-core.exe", "opennow-acceptance-verify.exe", "opennow_streamer_ffi.dll")
$files = Get-ChildItem build/qt-release/Release -File | Where-Object { $names -contains $_.Name }
if ($files.Count -ne 4) { throw "Expected exactly four release executables, found $($files.Count)" }
if ($files.Count -ne 4) { throw "Expected exactly four release binaries, found $($files.Count)" }
foreach ($file in $files) {
signtool sign /f $pfx /p $env:WINDOWS_PFX_PASSWORD /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 $file.FullName
signtool verify /pa /all $file.FullName
Expand All @@ -327,11 +335,15 @@ jobs:
$msiExpanded = Join-Path $env:RUNNER_TEMP "msi-expanded"
$process = Start-Process msiexec.exe -Wait -PassThru -ArgumentList "/a `"$($msi.FullName)`" /qn TARGETDIR=`"$msiExpanded`""
if ($process.ExitCode -ne 0) { throw "MSI administrative extraction failed" }
if (-not (Get-ChildItem $msiExpanded -Recurse -Filter opennow_streamer_ffi.dll)) { throw "MSI is missing the embedded streamer runtime" }
if (Get-ChildItem $msiExpanded -Recurse -Filter opennow-streamer.exe) { throw "MSI contains the obsolete standalone streamer" }
Get-ChildItem $msiExpanded -Recurse -Filter *.exe | ForEach-Object { signtool verify /pa /all $_.FullName }
$zip = Get-ChildItem build/release-artifacts -Filter *.zip
if ($zip.Count -ne 1) { throw "Expected one portable ZIP" }
$expanded = Join-Path $env:RUNNER_TEMP "portable"
Expand-Archive $zip.FullName $expanded
if (-not (Get-ChildItem $expanded -Recurse -Filter opennow_streamer_ffi.dll)) { throw "Portable ZIP is missing the embedded streamer runtime" }
if (Get-ChildItem $expanded -Recurse -Filter opennow-streamer.exe) { throw "Portable ZIP contains the obsolete standalone streamer" }
Get-ChildItem $expanded -Recurse -Filter *.exe | ForEach-Object { signtool verify /pa /all $_.FullName }

- name: Record artifact checksums
Expand Down Expand Up @@ -445,6 +457,8 @@ jobs:
mkdir -p build/release-artifacts
app="$RUNNER_TEMP/stage/OpenNOW.app"
test -d "$app"
test -f "$app/Contents/MacOS/libopennow_streamer_ffi.dylib"
test ! -e "$app/Contents/MacOS/opennow-streamer"
while IFS= read -r -d '' item; do
codesign --force --options runtime --timestamp --keychain "$OPENNOW_SIGNING_KEYCHAIN" \
--sign "$SIGN_IDENTITY" "$item"
Expand Down
14 changes: 7 additions & 7 deletions docs/core-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ most 512 items; overflow drops the oldest event and emits a diagnostic counter.
- `session.active.get`
- `session.remote.list`, `session.claim`, `session.ad.report`
- `streamer.detect`
- `streamer.prepare`
- `streamer.start`
- `streamer.status.get`
- `streamer.stop`
Expand Down Expand Up @@ -94,13 +95,12 @@ cached in the core process and bounded per response.
CloudMatch session methods preserve one client/device identity through create,
poll and stop, retain pending queue responses before signaling is available,
and return the complete ordered connection, ICE and negotiated-feature payload
needed by the native streamer. Streamer methods supervise the out-of-process
protocol-v4 runtime, secure NVIDIA signaling, SDP/ICE relay, health state and
bounded shutdown.
The shell supplies bounded global surface geometry on startup and debounced
move/resize updates at runtime. Both WebRTC and classic NVST receive a visible
surface before media presentation; the external native window remains behind
the transparent Qt guide layer while it owns gameplay input.
needed by the native streamer. `streamer.prepare` returns the normalized session
context used by the NVST runtime linked into the Qt shell. The in-process runtime
owns secure NVIDIA signaling, ICE/DTLS/SCTP, RTSPS, Mjolnir, RTCP and native
gameplay input, while Qt owns the graphics device, scene graph, video item and
all top-level windows. Legacy streamer lifecycle methods remain protocol
compatibility routes and are not used by the Qt shell.

`acceptance.export` is available only through the Qt shell's Diagnostics screen. It rejects
headless window systems and writes an atomic, redacted `opennow.live-acceptance` JSON file. The
Expand Down
39 changes: 17 additions & 22 deletions docs/qt-acceptance.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,12 @@ Use an authorized test account with no production secrets in reports. Sign in in
not put NVIDIA credentials, refresh tokens, signing keys, or notarization passwords in command
arguments, logs, issue trackers, or acceptance artifacts.

The presenter remains out of process. Windows reparents its HWND as a child of the Qt top-level
window. X11, Wayland and macOS use a paired native top-level window aligned to the stream region.
The surface contract carries a window handle, host-local and screen geometry, visibility, and scale,
but it does not establish cross-process Qt texture embedding. Foreign children and paired windows
cannot be covered reliably by ordinary Qt Quick items, so the shell hides the presenter before it
shows a QML menu, stats panel, reconnect screen, or error screen. The result is Qt-owned UI with a
temporarily suspended video surface, not a composited overlay over live video. No matrix row may
claim single-window composition or zero-copy into the Qt scene graph without a separate implementation
and measurement evidence.
The streamer is loaded by the Qt executable as an in-process Rust library. Platform decoders publish
native GPU frames through a bounded FFI mailbox; `StreamVideoItem` records conversion and
synchronization into the active QRhi command buffer and samples the imported texture in the Qt scene
graph. There is no child streamer process, child HWND or paired native video window. Acceptance must
still prove each platform's native texture import and synchronization on real hardware; the design
alone is not performance or zero-copy evidence.

## Performance evidence

Expand Down Expand Up @@ -56,7 +53,7 @@ requires `pass: true` for both reports. The hardware flag rejects offscreen/mini
software/null renderers, missing screens, and workloads that do not receive the requested physical
dimensions. It also rejects the test-only refresh-rate override, so release evidence always uses
the display-reported rate. This measures the Qt shell workload; it does not prove stream-window
embedding, native decoder throughput or a zero-copy handoff into Qt.
native decoder throughput or GPU texture-import behavior.

## Authorized stream evidence

Expand All @@ -66,32 +63,30 @@ ten minutes. Exercise the following without restarting the app:
1. Complete device login, account switching, subscription and region refresh.
2. Create a session, pass queue/ads if present, reach native NVST first-frame playback, and confirm
the live evidence reports `stream.transport: "nvst"`.
3. Open and close every guide and stats page while video is live. Confirm the presenter hides before
QML appears, returns after QML closes, never leaves a stale native handle, and transfers controller
input atomically. Record Windows as child-HWND behavior and the other platforms as paired-window
behavior rather than claiming a composited live-video overlay.
3. Open and close every guide and stats page while video is live. Confirm QML composes above the
scene-graph video item without suspending playback, stale frame tokens or input leakage.
4. Exercise keyboard, relative mouse, and every connected controller. Validate neutral controller
state after overlay entry, reconnect, pause, and resume.
5. Test window resize, fullscreen, display migration, the display's highest supported refresh
rate, and VRR/HDR only where the machine advertises them.
6. Load a profile that previously selected WebRTC or another legacy transport and confirm settings,
session creation, streamer status and exported evidence all resolve it to NVST. If a persisted
microphone mode is armed, confirm the runtime reports upstream audio as unavailable without
changing transport; microphone audio is not a release gate for the NVST-only client.
microphone mode is armed, confirm settings migration disables it without changing transport;
microphone capture is not part of the native runtime.
7. Capture a screenshot, start and stop a source-stream Matroska recording, play the resulting
media, verify the generated thumbnail, and reveal both files through the Media screen.
8. Rebind and exercise all eight stream shortcuts. Confirm stats and fullscreen reach Qt exactly once,
pointer lock remains native, microphone reports unavailable, screenshot, recording and stop reach
the shell exactly once, and anti-AFK produces an F13 pulse after four minutes without leaking the
key into the game.
8. Rebind and exercise all seven active stream shortcuts. Confirm stats and fullscreen reach Qt
exactly once, pointer lock remains native, screenshot, recording and stop reach the shell exactly
once, and anti-AFK produces an F13 pulse after four minutes without leaking the key into the game.
9. Enable the anti-AFK indicator/reminder and session clock, then confirm the post-session report
reflects NVST transport, elapsed time, backend, first-frame latency, recovery/error counters and
diagnostics navigation.
10. Exercise favorites, entitlement-filtered aspect ratio/resolution/FPS choices, keyboard layout,
game language, console-friendly launch and in-game-settings persistence on a title that advertises
the corresponding NVIDIA feature.
11. Force one recoverable network interruption and one streamer-process failure. Confirm bounded
reconnect/restart behavior, no stuck input, and a usable error if recovery is exhausted.
11. Force one recoverable network interruption and one graphics-device or native-runtime failure.
Confirm bounded reconnect/reinitialization behavior, no stuck input, and a usable error if
recovery is exhausted.
12. Export both the redacted diagnostic report and **live evidence** from the Diagnostics screen
after the run. The live export is direct machine-readable JSON and must report
`observedPass: true`; it includes hashed screenshot/recording/thumbnail metadata and bounded
Expand Down
Loading
Loading