Skip to content

release: promote OpenNOW 1.0.4 to main - #1335

Open
zortos293 wants to merge 100 commits into
mainfrom
dev
Open

zortos293 wants to merge 100 commits into
mainfrom
dev

Conversation

@zortos293

@zortos293 zortos293 commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Promote Qt/native 1.0.4 from dev to main. Dev has advanced beyond the audited nightly source b3fe710. Later changes cover Linux HID identity, Windows modifier-side/AltGr routing, Wayland Vulkan swapchain teardown, native mouse latency diagnostics, consuming launch intent after successful session creation, blocked flight-device statistics, and the sender-timing/bounded-presentation integration from #1298. The current streamer boundary is FFI ABI 18; the historical ABI-16 nightly and earlier Qt test counts below do not validate this new native integration.

The PR's live head must pass its own current checks and completed review before promotion. Earlier b3fe710 verification does not establish readiness of later commits. Main promotion also remains held for explicit merge confirmation.

The previous release audit fixed allocation/cancellation, account/settings scope, graceful shutdown/recording finalization, stream/ad identity, native startup/input bounds, decoder/color policy, authenticated packaging/Decky installs, modal keyboard ownership and Vulkan Wayland framing. Acceptance-only #1357 preserves SDL virtual controller edges without changing normal input or weakening assertions. Verified head88dba73e and devb3fe7102 have identical tree0e1e99dccda9571900880512db6b41a35e57d469. That source passed575 Qt tests, six focused targets,48 overlap repeats, seven PR CI checks and clean Capy review. Older raw machine-local logs were lost in workspace replacement; durable conversation/CI/review evidence remains. The original ARM64 stall was not instrumented and is not claimed as uniquely explained.

Published nightly v1.0.4-nightly.1515.1 remains verified at exact b3fe710, not the later dev tip. Run40598756074 passed native ARM64 packaging/acceptance, signing and publication. Independent public downloads passed the complete30-file inventory,14 Ed25519 manifests,29 checksums and exact release-tag/source verification. Public notes retain platform-signing warnings and hardware/recording limitations. Earlier1511 attempts were not published: GitHub HTTP500, then the repaired ARM64 acceptance-driver failure.

The owner waived unavailable physical GPU/Wayland validation and chose community issue feedback. ZOR-37 is canceled as waived, not passed. Historical cloud baseline/fixed failures do not establish a driver root cause or physical-GPU validation. Other hardware/gameplay coverage is not claimed. Recording initial A/V offsets and their real-world synchronization impact remain unmeasured. For #1298, live sender-paced activation, accepted server Cloud G-Sync and the reported 240-FPS smoothness gap remain unverified. It does not invoke the private NVIDIA VRR setter or establish physical scanout/Reflex behavior; retain the feature's documented hardware limitations in future release notes.

Specific main-merge confirmation is still requested in ZOR-38; no main promotion, production candidate or stable publication has occurred in this release task. Production must build an exact-main immutable candidate, verify inventory/checksums/update signatures and required platform signing, then promote those same bytes without rebuilding. The hardware waiver bypasses none of these checks. Retain the validation limits and existing platform-signing warnings in public notes.

zortos293 and others added 30 commits October 16, 2026 08:22
… 148 (#1237)

* fix(qt): show stream notices at the minimum window size with stats open

The stream toast column gated its own visibility on a fixed 148 px budget.
At the 960x540 minimum with the stats overlay expanded the panel pushes the
column to y 378, so the budget demanded 526 of the 516 available and the
column hid itself.  Because `active` was defined as `visible && streaming`,
hiding destroyed the queued notice instead of deferring it: neither the
colour-format notice nor the HDR downgrade notice was ever created.

Gate on the column's real stack height instead, and arm it independently of
its own visibility so a stack that does not fit is deferred rather than
dropped.  Without the second clause the gate oscillates whenever the stack
genuinely does not fit, because hiding collapses the height to zero.

Pre-existing defect: DesktopStreamOverlayHost.qml is absent from #1232's
diff and the gate is byte-identical at its merge parent.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(qt): keep stream notices pending until they are actually presented

Review found that a hidden stack kept running the notice observers, so
claimHdrNotice persisted the game as shown and the four-second timers
expired while the notice was never on screen.  Leaving stats open past
the timeout discarded the notice and permanently suppressed that game.

Presentation of the two consuming notices now sits behind the column's
visibility: a notice that cannot fit stays queued with no claim and no
display lifetime until there is room for it.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(qt): hold the packet-loss notice while the toast stack is hidden

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…den (#1241)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
… ZIP only (#1243)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…ays a real RTT (#1242)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…keyframes; flag decode-bound streams (#1244)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…riting the host's (#1245)

* fix(release): configure candidate graphics and signing keychains (#931)

* fix(release): pass inline requirements to macOS codesign (#932)

* Add Android download link to README

Added a link to download the Android build in the README.

* Update README.md

* fix(qt): map regional keyboards from physical keys (#1010)

* Fix:
1. Dead keys (å ä ö § ´ ¨ and the rest)
2. No keyboard layout setting
3. Clock 4 hours off

* fix(qt): complete regional keyboard mapping and session layout ownership

---------

Co-authored-by: Thorulf <228320212+Thorulf@users.noreply.github.com>

* release: prepare stable v1.0.2 (#1086)

* fix(streamer): negotiate compatible Alliance video SETUP forms

* chore(streamer): diagnose rejected video Transport fields safely

* fix(linux): discover host VA-API drivers in AppImage launches (#935)

* feat(qt): explain cloud session setup and native connection progress

* fix(session): preserve terminal and allocation ownership across recovery

* test: align recovery and package fixtures with session contracts

* fix(auth): enforce credential and account lifecycle ownership

* fix(auth): restore secure grants when legacy cleanup is deferred

* fix(alliance): scope provider routing and session ownership

* fix(session): retain seat ownership across account generations

* style(auth): center the desktop sign-in card

* fix(catalog): track complete library and sync convergence

* fix(catalog): preserve traversal ownership across launch and sync

* feat(library): add cloud ownership and favorites with guarded launches

* feat(search): query remote games from the command palette

* fix(search): preserve local command keyboard activation

* feat(settings): share validated language and stream profile choices

* fix(test): locate bundled core through the application target

* fix(catalog): omit campaign dependency from targeted game lookups

* fix: restore authenticated launches and display chroma fallback (#949)

* fix(diagnostics): retain redacted catalog error details

* fix(catalog): send linked-account queries as JSON

* fix(catalog): accept nullable definition lists

* fix(catalog): normalize the no-subscription sentinel

* fix(streaming): display chroma fallback and notify once (#950)

* fix(streaming): preserve playback on chroma-only fallback

* feat(streaming): notify once when video color format changes

* test(qt): run Windows color notices on the native platform

* fix(streaming): debounce connection warnings and clear them on recovery (#952)

* fix(ui): keep game details padded and accessible (#953)

* fix(ui): keep game details padded and accessible

* fix(core): reject requests outside the negotiated ready state

* fix(test): polish game-details layout before measuring it (#954)

* Fix bounded Opus PLC on the embedded Linux audio path

* Preserve physical gameplay key identity on Linux

* Isolate embedded Linux audio failures from video

* Keep Discord presence on a bounded persistent IPC connection

* Preserve bounded Windows cancellation recovery for Discord

* Publish Linux video readiness after entering Running

* Satisfy Windows Clippy for Discord pending I/O errors

* Validate VAAPI color profiles on the selected decode device

* Keep Qt and core media paths on the same Pictures root

* Make media path regression tests portable to Windows

* Carry validated display luminance into HDR session requests

* Measure native frame stages and Qt presentation progress

* Include presentation timing tests in the CI target contract

* Launch the eligible Steam client for persistent storage management

* Publish decoder progress even when frame admission stops

* Keep store launch progress and failures on the storage screen

* Select explicit embedded Linux software decoding

* Gate 360 FPS on entitlement and effective hardware decoding

* feat(core): receive account push updates natively

* feat(streaming): negotiate bandwidth-saving quality policy

* feat(network): measure selected-zone UDP payload reachability

* fix(streaming): recover stalls from actual frame progress

* Make push lifecycle tests deterministic under contention

* Receive complete UDP probe replies before correlation

* Pace software decode fixture using observed decoder admission

* Forward complete Sony snapshots through session-owned native HID

* Keep nightly versions ahead of published stable releases

* fix: send private NACK-v2 feedback for Mjolnir video

* fix: pace Mjolnir NACK retries using current RTT

* feat: add desktop PrintedWaste queue selection

* fix: require queue capability during core handshake

* fix: align NVST color negotiation with the official client (#981)

* fix(updater): repair discovery, portable installation and failure reporting (#982)

* fix(updater): repair release discovery and Linux update delivery

* fix(ci): use verified Ubuntu mirror failover

* fix(updater): repair portable replacement and surface install failures

* refactor: simplify and align desktop settings (#983)

* refactor: simplify and align desktop settings

* fix: preserve resolution stepper and frame rate pills

* fix: tighten settings spacing and expose codec selection

* fix: bound each network probe attempt by its original deadline

* fix(ui): unify brand lockup and repair desktop misalignments (#984)

Replace the hand-drawn onboarding mark with a shared DesktopBrandLockup
backed by a hi-res mark cropped from the canonical logo, and fix the
misaligned, clipping, and out-of-reach desktop elements found in the
screenshot audit: About brand row and toggle icons, empty store shelf
headers colliding with the loading state, compact settings nav sections
cut off past the fold, dead Updates header search, library cell geometry
that clipped artwork at scale, and the empty-home See all count.

* fix(streamer): harden IPv6 RTSPS endpoints without widening trust

* fix(qt): repair provider, login, account and recovery flows

* fix(test): run queue selector tests in the production Basic style

* fix(test): cover queue selector tests in the CI unit-test contract

* fix(test): set production font in queue selector tests for warning-free runs

* fix(test): load bundled mono fonts in queue selector tests

* fix(test): select Basic style via environment to avoid a new Windows DLL dependency

* fix(test): scope queue selector CI coverage off Windows pending local diagnosis

* fix(alliance): fall back Digevo prod endpoint to LATAM West

NVIDIA discovery still advertises https://prod.DIG.geforcenow.nvidiagrid.net/
for Digevo, but that hostname has no DNS records globally, so region
discovery, VPC lookup, and all CloudMatch routing for Digevo accounts
failed. Route Digevo to the working regional endpoint
https://latam-west.dig.geforcenow.nvidiagrid.net/ (server NPA-DIG-SCL-01,
region LATAM West), verified live with serverInfo, session list, session
create, and first video frame at 1080p60 H264.

The fallback stays within the existing HTTPS nvidiagrid.net trust policy;
no protocol or ABI change.

* fix(alliance): engage provider fallback only when discovery host is unreachable (#991)

Follow-up to the Digevo LATAM West fallback. Alliance prod.* endpoints
are geo-steered: prod.dig resolves to the nearest PoP (e.g. LATAM North
via Colombia) from inside the footprint and NXDOMAINs outside it. The
unconditional rewrite forced all Digevo users onto LATAM West and broke
steering for North users.

The fallback table now engages per-provider only when the advertised
host fails DNS resolution, keeping native geo-steering untouched when
prod works (VPN / in-footprint) while still rescuing out-of-footprint
users. Resolver is injectable for deterministic tests.

* streamer(vulkan): prioritize capable dGPUs and fix AppImage GLib clash

- Filter and score Vulkan physical devices based on video decode queue
  isolation and PRIME offload variables instead of indexing device 0.
- Fall through to alternative physical devices when queue isolation fails.
- Exclude host GLib and GObject binaries from the AppImage packaging pipeline
  to resolve host VA-API driver dlopen symbol clashes.
- Ensure proper sync object drain before Vulkan teardown under Wayland.

* fix(test): isolate Vulkan device scoring preferences from process env

Score computation now takes an explicit VulkanDevicePreferences value, with
a thin from_env wrapper preserving the OPENNOW_VK_* / PRIME behavior. The
unit tests exercise the pure function directly, so they no longer mutate
process-wide env vars and cannot flake when cargo runs them in parallel.

Also addresses review nits: sort_by_key, drop the redundant unsafe around
the safe options builder, and fall back to default device selection if the
index string ever fails CString conversion instead of unwrapping.

* fix(streamer): re-sweep video SETUP on a bounded pace when the rig has no peer yet

A rig whose video streamer is still starting answers SETUP with 200 but
an empty Transport header. The negotiator fired all URI/Transport forms
in under a second and terminally failed with missing-video-peer, never
giving the rig a chance.

Re-sweep on a bounded pace (3s pauses, at most 3 extra rounds) when at
least one 200 arrived without a usable peer, inside the existing 20s
request budget. Pure rejections still fail fast, the first SETUP request
is byte-identical, and the terminal missing-video-peer code is unchanged
after exhaustion. New video-setup-retry log line keeps rounds observable
without logging addresses.

* fix(streamer): align 10-bit color negotiation and codec gating with official client

CloudMatch now sends the official 15-field requestedStreamingFeatures set (codec, bitrate, vsync, channel count, QoS, touch, and dynamic policy stay client-side/RTSP-only); session metadata drops non-official pairs and externalAppId. Codec selection stays client-side end to end with settings fallbacks, and HDR validation uses the normalized codec.

NVST ANNOUNCE states color explicitly per the vendor capture (literal bitDepth plus chroma_format_idc, dynamicRangeMode for HDR only) and carries maxCodec/maxH264 profile/level for encoder init, replacing the delta-suppressed 0/1 encoding that left seats unable to start Main10 video.

Settings pages grey out codecs per selected color quality via core-resolved descriptors (H.264 is 8-bit 4:2:0 only, AV1 is 4:2:0 only), gate 10-bit/HDR on Ultimate/Performance membership and 4:4:4 on Ultimate plus Windows/macOS, and heal persisted invalid codec/color combos toward Auto on first launch.

* fix(lint): satisfy rustfmt and clippy 1.96 nonminimal-bool

rustfmt formatting for the color-negotiation changes; clippy-suggested De Morgan simplifications including two pre-existing nonminimal-bool sites surfaced by the current stable toolchain.

* fix(lint): drop unused mut in codec omission test

* fix(qml): pass codec argument through in codecDisabledByProfile

ReferenceError (code vs codec) broke the desktop codec row bindings whenever decoder capabilities were present, failing settings-streaming acceptance on capability fixtures.

* fix(vaapi): export the DRM PRIME surface after it is decoded into

`to_native_handle` exported the surface immediately after allocating it,
and every decoded frame then replayed that cached descriptor.

A driver is not required to have the final buffer ready at allocation
time. `libva-v4l2_request-avd` documents both halves of the problem: a
surface exported before it is ever decoded into gets standalone dma-buf
backing that "is dropped once the surface is bound to a real decode
context", and its CAPTURE queue "is configured lazily on the first frame,
after the codec controls (SPS etc.) have been set". The cached descriptor
therefore refers to a throwaway allocation whose geometry was never
negotiated, and the picture is unusable while decoding itself succeeds.

Defer the export to first use, which happens after `sync()` in
`drain_events`, and cache it from there. The driver returns a stable
dma-buf per VASurfaceID once the surface is bound, so the descriptor
stays valid for the frame's lifetime.

cros-libva deliberately prevents constructing its safe descriptor outside
the crate, so the deferred export uses `vaExportSurfaceHandle` directly
and owns the returned file descriptors.

* fix(vulkan): import DMA-BUF frames at their coded height

A decoder may pad its output taller than the visible picture; the Apple
AVD block emits 1920x1088 for a 1080p stream. The chroma plane then sits
at `pitch * 1088`, but the imported image was created at the visible
height, so a driver that derives plane offsets from the image extent
rather than honouring the explicit plane layout read chroma from
`pitch * 1080` instead. Luma is unaffected because it starts at offset 0,
so the picture keeps its shape and only its colour is displaced: a
vertically offset ghost of the image in its own chroma.

Derive the coded height from the exporter's chroma offset and create the
image with it, so both readings agree whether or not the explicit layout
is honoured, then scale sampling by `visible / coded` to keep the padding
rows off screen. When there is no padding the scale is 1.0 and the path
is unchanged.

The fragment shader scaled texture coordinates about the centre, which
would shift the picture by half the cropped padding; anchor it at the
origin instead. Existing callers pass a scale of 1.0, for which both
forms are identical.

Also fix two spec violations in the same importers, which is what allowed
the explicit layout to be disregarded in the first place:

  * `VkSubresourceLayout::size` must be 0 in every element of
    `pPlaneLayouts`
    (VUID-VkImageDrmFormatModifierExplicitCreateInfoEXT-size-02267).
  * DMA-BUF images produced by V4L2/VA-API come from a foreign queue
    family, not another Vulkan device, so the acquire and release
    barriers must use `VK_QUEUE_FAMILY_FOREIGN_EXT`. The SAND importer
    already did this; the generic one did not.

* fix(core): carry width and height in the negotiated stream profile

The profile exposed the negotiated geometry only as a `resolution`
string. `ShellStore.normalizedStreamingSession` splits it into `width`
and `height`, but the desktop stream screen reads those fields off the
session it is given, so they can arrive undefined. `StreamVideoItem`
then receives `Qt.size(0, 0)`, and `aspectFitRect` returns the full item
rect for an empty video size, stretching the picture to the window.

On a 16:9 display this is invisible, because filling the window is also
the correct result. On anything else the picture is distorted: a 16:9
stream on a 3024x1964 panel is stretched about 15% vertically. The same
rect drives input mapping, so pointer coordinates are mapped through the
distorted geometry too.

Emit `width` and `height` next to `resolution` so consumers do not have
to re-parse the string. Both are null when the monitor does not report
its geometry, which is the existing behaviour.

* fix(streamer): stop SETUP at first 200 and fall back to bundle peer when rigs omit Transport (#998)

Live YES/ABYA rigs 200 the first video SETUP with an empty Transport
header and 400 every further form; ANNOUNCE after such a burst is then
rejected, while a single SETUP followed by ANNOUNCE succeeds. Sweeping
all forms poisons these sessions, and the old code did exactly that.

Stop each sweep at the first 200 (with or without a peer). Peerless 200s
still re-issue the same stopped-at form on the bounded #993 pace for
late-starting encoders, and terminal exhaustion with a CloudMatch bundle
peer proceeds on it (mirroring the official client, which carries on when
the server omits transport) instead of failing the seat. Pure rejections
still fail fast; the first SETUP request is byte-identical.

Also adds redaction-safe DESCRIBE shape logging and an
OPENNOW_STREAMER_LOG_FILE sink for the standalone child so headless
negotiation failures stay diagnosable.

* fix(vulkan): apply the coded-height import to the standalone presenter

Review follow-up to "import DMA-BUF frames at their coded height".

The coded height was derived by dividing the chroma offset by the luma
pitch, which silently assumes the luma plane starts at offset 0. Both
offsets are relative to the same object and the importers accept a
nonzero luma offset, so a buffer placed after a header reported too many
rows and cropped a picture that needed no cropping. Derive it from the
distance between the two offsets instead.

That derivation now lives in `format.rs` as `nv12_coded_height`, shared
by both importers and unit-tested directly, since it needs no device.
The divisibility and visible-height guards are unchanged, and anything
that does not describe a cleanly padded pair still falls back to the
visible height.

`VulkanPresenter` carried none of this. It is a second DMA-BUF importer
with its own shader, so a padded buffer kept exactly the chroma ghost
the other path had just been fixed for. It now imports at the coded
height and crops in `nv12.frag`.

`nv12.frag` already had a `texture_scale`, but it is the letterbox
mapping: centred, with the bars painted black outside the source. The
padding crop has to be anchored at the origin and applied after the
bounds test, so it is a separate `coded_scale` rather than a change to
the existing factor. It is `[1.0, 1.0]` for every unpadded source, which
is every upload path. `nv12.frag.spv` is regenerated with `glslc -O`,
which is what the tracked binary was built with.

The presenter also still acquired and released its imported images from
`VK_QUEUE_FAMILY_EXTERNAL`, promising a matching release from a Vulkan
device that never comes. Both barriers now use
`VK_QUEUE_FAMILY_FOREIGN_EXT`, and `VK_EXT_queue_family_foreign` joins
the extensions the presenter probes and enables: without it the import
has no legal way to take ownership, so it belongs in the contract rather
than being optional.

For the embedded path the extension was already requested, but
`hasDmabufImportContract` did not require it while the image importer had
started issuing foreign-queue transfers. It does now, matching
`hasDmabufBufferImportContract`.

* fix(streamer): speed up software decode and NV12 upload

Cap FFmpeg software frame threads and set low-delay so extra logical
CPUs do not add a frame of latency each. Pack YUV420P into tight NV12
without a bilinear scale, and keep the Vulkan upload buffer mapped.

Co-authored-by: Zortos <zortosdev@proton.me>

* fix(streamer): keep a queued keyframe when the D3D11 decode queue overflows

Intel iGPU sessions were falling through to the Microsoft software H.264
decoder, then wiping the only queued IDR on every overflow. That requested
a new keyframe for each delta and froze the picture. Preserve a queued
keyframe, ask for one IDR per gap, and try unscoped hardware MFTs before
the software fallback.

Co-authored-by: Zortos <zortosdev@proton.me>

* fix(streamer): keep software decode slice-threaded and drop corrupt frames

Frame threading still held about one frame per worker before the first output.
Slice-only threading keeps the 2–4 thread budget inside a single frame.
Corrupt software frames now fail as ReferenceLost so recovery matches V4L2.

Co-authored-by: Zortos <zortosdev@proton.me>

* fix(qt): choose a hybrid GPU that can hardware-decode

Automatic indexed only the high-performance adapter, so a GeForce MX110
with no decoder failed the embedded session before Intel HD Graphics 620
was considered. Index H.264, HEVC, and AV1 on each adapter, select the
first one that can decode, and list those codecs under Stream settings.

Co-authored-by: Zortos <zortosdev@proton.me>

* fix(windows): try untagged hardware decoders before the Microsoft MFT

Intel Quick Sync does not publish MFT_ENUM_ADAPTER_LUID, so a hardware
search filtered to the Iris Xe adapter is empty and the Microsoft H.264
MFT decodes on the CPU. When that filter matches nothing, enumerate
hardware transforms with no adapter attribute first.

Co-authored-by: Zortos <zortosdev@proton.me>

* fix(streamer): satisfy clippy and the Windows HID start test

hold_keyframe_request is only called from the embedded D3D11 path, so
non-Windows clippy rejected the library as dead code. Gate it the same
way as the other Windows queue helpers, and keep the unit tests.

The HID lifecycle test sent video to a closed UDP port. On Windows that
resets the socket, the bundle thread exits, and HID unbinds before the
test can see the binding start() installed. Hold a live peer instead.

Co-authored-by: Zortos <zortosdev@proton.me>

* fix(streamer): ignore Windows ICMP port unreachable on NVST UDP

Windows reports a closed peer as WSAECONNRESET. The video receiver
treated that as fatal, exited, and closed the HID endpoint while the
session was still connected. Disable SIO_UDP_CONNRESET on NVST sockets
and keep a connection-reset receive in the poll loop.

Co-authored-by: Zortos <zortosdev@proton.me>

* fix(qt): stop windows.h from expanding the decoder profile bound

MSVC defines min as a macro, so std::min in the adapter decode index
was a syntax error. Bound the profile count with a comparison and
include Windows headers with NOMINMAX.

Co-authored-by: Zortos <zortosdev@proton.me>

* fix(streamer): keep HID bound when Windows reports ICMP port unreachable

A hole-punch ping to a closed UDP peer makes Winsock return WSAECONNRESET.
Treating that as a fatal receive error exited the NVST worker and closed the
HID endpoint before the session start assertion. Disable SIO_UDP_CONNRESET on
reserved sockets and ignore ConnectionReset on the UDP receive and send paths.

Co-authored-by: Zortos <zortosdev@proton.me>

* fix(updates): quote MSI INSTALL_ROOT to avoid msiexec 1639 and clear terminal transactions (#996)

* feat(update): install Windows with setup.exe and swap the ZIP

New installs copy the portable tree to %LOCALAPPDATA%\OpenNOW without an MSI product. Later updates keep the signed ZIP rename path. An MSI-registered tree is told to replace itself once with setup.exe and is not handed to msiexec.

Co-authored-by: Zortos <zortosdev@proton.me>

* fix(release): require setup.exe in the platform artifact set

The candidate inventory already publishes Windows setup.exe, so nightly
assembly and promotion must treat those files as required packages.
Signature and checksum checks stay exact; only the frozen counts of ten
and twelve platform artifacts change.

Co-authored-by: Zortos <zortosdev@proton.me>

* fix(qt): map German gameplay keys from the physical position

Windows forwarded the active layout's virtual key, so ü was sent as the
ö position and the hyphen key was sent as ß. Gameplay input now uses the
set-1 scan code's US position, which is what the remote layout expects.

Co-authored-by: Zortos <zortosdev@proton.me>

* fix: keep a 220 kbps bitrate request below the 1 Mbps floor

A configured max bitrate such as 0.22 Mbps is stored and announced as
220 kbps instead of being raised to 1000 kbps. A missing max bitrate
stays 75 Mbps, and whole-number settings remain JSON integers.

Co-authored-by: Zortos <zortosdev@proton.me>

* fix(qt): map macOS gameplay keys from the physical position

macOS hardware key codes now select the US virtual key on every layout, matching the Windows scan-code and Linux XKB paths. German ü and hyphen stay on their physical positions, and AZERTY or Cyrillic characters cannot move the key.

Co-authored-by: Zortos <zortosdev@proton.me>

* fix: resolve pre-nightly reliability and packaging audit findings (#1011)

* fix: resolve pre-nightly reliability and packaging audit findings

* fix: port regional keyboard mappings onto dev

* fix(ci): restore timestamped package compiler caches on all platforms (#1012)

* perf(streamer): skip stale decoded frames before conversion (#1016)

* fix(streamer): use bundle peer without retrying SETUP (#1015)

* feat(qt): redesign stream shortcut settings (#1017)

* fix(qt): allow clearing stream shortcut bindings

* test(qt): assert clearable stop shortcut contract

* feat(qt): redesign shortcut bindings with inline editing

* fix(qt): keep shortcut bindings alive through teardown

* fix(qt): expose missed UI labels to localization (#1018)

* fix(streamer): encode the native microphone in 10 ms frames (#1014)

* fix(streamer): encode the native microphone in 10 ms frames

The native microphone uplink reached the network, but the cloud PC's
GeForce NOW microphone stayed silent: Steam's voice test showed no input
on any device.

The server's mic config sets `mic.frameSize:10`, and the uplink sent
20 ms Opus frames. Encoding 10 ms frames (480 samples at 48 kHz) is
enough to make the microphone audible, with nothing else changed.

Tested end to end on Linux (Asahi) by trying one change at a time.
Sending on SSRC 5 (the SSRC the server gives the browser client), on the
game-audio mid, and wrapped as RED level 3 all left the microphone silent,
even combined, while 20 ms frames were used. The 10 ms change alone, on
the existing SSRC 1 / mid 2 / plain Opus path, made it work.

* test(streamer): name the microphone encoder test for its 10 ms frame

* fix(streamer): recover stalled video before decoder submission (#1019)

* fix(streamer): recover VA-API frame export failures (#1020)

* fix(streaming): route Alliance sessions and align NVST feedback (#1022)

* fix(streamer): preserve SDR colorimetry across resolution and HDR transitions (#1021)

* fix(streamer): preserve SDR colorimetry across HDR transitions

* test(streamer): accept explicit SDR output before HDR transition

* fix(streamer): align custom SCTP channel reliability (#1024)

* fix(streamer): preserve server-owned ANNOUNCE settings (#1025)

* fix(streamer): leave unverified QoS v7 fields unset (#1027)

* fix(core): advertise native WebSocket signalling metadata (#1023)

* fix(streamer): gate input activation until PLAY succeeds (#1026)

* fix(streamer): batch and negotiate QoS feedback (#1028)

* fix(test): exercise current fullscreen stats shortcut in smoke (#1030)

* fix(core): persist large Windows sessions securely across restarts (#1031)

* fix(core): persist large Windows sessions securely across restarts

* test: make Windows credential migration failure fixture portable

* fix(streamer): honor negotiated pacing feedback mode (#1033)

* fix(streamer): derive bundle ICE identity from advertised port (#1035)

* fix(qt): reset queue poll failure budget on success (#1036)

* fix(streamer): correct Linux frame pacing mismatch thresholds (#1037)

* fix(core): gate Auto AV1 on known capabilities (#1040)

* fix(streamer): bound backlog and preserve reference recovery (#1042)

* fix(core): cancel fresh CloudMatch sessions on returned control host (#1034)

* fix(core): cancel fresh sessions on returned control host

* fix(core): fail closed on persisted session cleanup stops

* fix(core): preserve explicitly selected CloudMatch zones (#1038)

* fix(core): encode HDR minimum luminance in protocol units (#1039)

* fix(streamer): honor bounded negotiated QoS batch size (#1044)

* fix(streamer): avoid startup IDR and probe primary port (#1041)

* fix(streamer): omit unused channels on dedicated video (#1043)

* fix(windows): keep SDR matrix through HDR output transition (#1045)

* diagnostics(windows): describe decoder color mismatch

* fix(windows): follow SDR matrix on HDR output transition

* fix(streamer): report measured frame receive timing

* fix(streamer): report measured bandwidth feedback

* diagnostics(streamer): record reported bandwidth with received video bytes

* fix(hdr): use measured Windows monitor metadata for native HDR

* fix(stats): report measured stream UDP receive rate

* fix(qt): preserve exclusive controller selection on disconnect (#1061)

* fix(diagnostics): preserve safe stream recovery and decode progress (#1062)

* fix(qt): restore Swedish OEM input positions (#1063)

* fix(streamer): rearm decode watchdog after recovered output (#1064)

* fix(qt): retire stale polls when replacing store sign-in (#1065)

* fix(qt): retire polls when replacing account sign-in

* fix(qt): keep intentional cancellations out of global errors

* fix(streamer): preserve atomic updates across Rust versions (#1073)

* fix(streamer): poll media deadlines after STUN datagrams (#1066)

* fix(build): run the license generator for the Rust host target (#1068)

* fix(linux): select a usable native H.264 VA-API device (#1069)

* fix(qt): confirm native window close without disturbing sessions (#1070)

* test(qt): cover native window close confirmation

* fix(qt): confirm native window close without disturbing sessions

* fix(startup): enforce process-lifetime profile and endpoint ownership (#1071)

* fix(streamer): keep unresolvable packet trains from lowering bandwidth estimate (#1057)

A frame whose packets arrive within the receive clock's resolution (most
often a single-packet frame in a static scene) has its receive span
clamped to 0.5 ms, so bytes/0.5 ms was averaged in as measured capacity.
For a ~1 KB packet that is ~16.5 Mbps, which the server then honored,
shrinking frames further and holding the stream at a low bitrate.

Treat trains shorter than 1 ms as a lower bound: they may raise the
estimate but no longer drag it down.

Co-authored-by: Dinoway <26033046+Dinoway@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(core): send clientTimezoneOffset as local minus UTC (#1058)

#877 replaced the hardcoded 0 with UTC-minus-local milliseconds, but
CloudMatch applies the value as local minus UTC. A client at UTC-5 sent
+18,000,000 and the VM clock came up at UTC+5, ten hours ahead. Android
already sends local minus UTC via TimeZone.getOffset.

Flip the sign and cover it with fixed-offset tests, since the existing
test compared the body against the same expression it was built from.

Co-authored-by: Dinoway <26033046+Dinoway@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* fix(auth): add private JSON fallback for unavailable credential stores (#1076)

* fix(desktop): reconcile restarted sessions and serialize settings writes (#1074)

* fix(desktop): close account, settings, and recovery audit gaps (#1082)

* fix(desktop): close account, settings, and recovery audit gaps

* test(core): isolate media CLI profiles with atomic temporary directories

* fix(updater): tolerate transient Windows metadata readers

* fix(qt): label 4:4:4 color profiles as coming soon (#1083)

---------

Co-authored-by: Zortos <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: Kief5555 <67562560+Kief5555@users.noreply.github.com>
Co-authored-by: quadsteel <blacksteel@outlook.com.gr>
Co-authored-by: makhov <makhov.alex@gmail.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cursor[bot] <206951365+cursor[bot]@users.noreply.github.com>
Co-authored-by: Dinoway <Dinowaygaming@gmail.com>
Co-authored-by: Dinoway <26033046+Dinoway@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* fix(flatpak): pin QT_QPA_PLATFORM to wayland;xcb in the sandbox

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: Kiefer <67562560+Kief5555@users.noreply.github.com>
Co-authored-by: Thorulf <228320212+Thorulf@users.noreply.github.com>
Co-authored-by: quadsteel <blacksteel@outlook.com.gr>
Co-authored-by: makhov <makhov.alex@gmail.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cursor[bot] <206951365+cursor[bot]@users.noreply.github.com>
Co-authored-by: Dinoway <Dinowaygaming@gmail.com>
Co-authored-by: Dinoway <26033046+Dinoway@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…1247)

* feat(qt): add --fullscreen and --quit-after-session launch options

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(qt): add --fullscreen and --quit-after-session launch options

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(qt): quit after a server-ended session; drop the stray plugin archive

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…up (#1248)

* fix(qt): hold the quit while a replacement launch is set up

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(qt): hold --quit-after-session while a replacement launch is set up

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: drop the stray plugin archive

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…ops crackling (#1251)

* fix(linux-audio): keep a 40 ms lead in the pw-cat pipe so playback stops crackling

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(linux-audio): leave the pw-cat pipe at its default capacity

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…no in-stream hint bar (#1250)

* feat(deck): Steam Deck identity by default, View+Menu session chord, no in-stream hint bar

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(deck): hold the first chord button briefly so View+Menu never reaches the game

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(deck): time the View+Menu chord per event and never replay chord taps together or across capture

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(deck): end a pending chord tap before recording the next chord press

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(decky): add an OpenNOW plugin for Steam Game Mode

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(decky): confirm before switching games, wait for exit, lock settings writes, flag unknown versions

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(decky): implement design B (slim launcher, full-screen settings, play-bar button)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(decky): run flatpak with the host's libraries, not Decky's bundled ones

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(decky): put Play on OpenNOW in Steam's new game-page play bar

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(decky): play from the game page through the OpenNOW shortcut instead of adding one

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(decky): set shortcut launch options with SetShortcutLaunchOptions and restore them once OpenNOW starts

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(decky): resume the game already streaming and report when OpenNOW doesn't start

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(decky): ask whether to resume or restart a game that's already open in OpenNOW

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(decky): wait for the catalog before matching a game page

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ci(decky): run the plugin workflow on a Blacksmith runner

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore(decky): stop tracking the built plugin zip

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ci: run the required Qt checks on Decky plugin pull requests

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…he keyring (#1252)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(decky): let the plugin install and follow OpenNOW nightlies

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(decky): keep the new channel's release when an old-channel install finishes

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…lugin download link (#1254)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(decky): detect and install OpenNOW on DroidDeck

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(decky): bound the DroidDeck helper and keep the arch lookup off the event loop

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(decky): keep the watchdog until the DroidDeck helper is reaped

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…l it's fixed (#1256)

* fix(auth): measure ID-token expiry from its lifetime, not the local wall clock

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(auth): refuse sign-in and warn when the system clock is wrong

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…s advice (#1261)

* fix(qt): wrap the decoder-limited notice instead of eliding its advice

The decoder-limited stream notice carries a 94-character instruction. Without
formatNotice it took the single-line path, so the half of the sentence that says
what to do was cut off. Every other long-copy notice in the stack already wraps.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(qt): pin the decoder notice's wrapped copy at three window sizes

The notice could be raised by no shipped fixture, so nothing painted it. The new
fixture drives the decoder-limited state through ShellStore's queue-dropped hook
and asserts the instruction is not truncated, wraps, grows the notice, and stays
inside the window.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(qt): reserve the toast column for the notices that are showing

The gate reserved pendingNotices x tallest-child-height, so a tall notice
reserved its height for every pending notice even when it was not on screen.
Summing the pending notices keeps a wrapped notice honest: it reserves the
wrapped height only while it is the one on screen. The estimate still reads real
child heights, so there is no fixed-height constant and no loop through the
column's own visible.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…k identity (#1259)

The core turns identifyAsSteamDeck on once on Steam Deck hardware and records
steamDeckIdentityDefaultApplied so it never overrides the choice afterwards.
The plugin wrote identifyAsSteamDeck without that marker, so a user who turned
identity off in the plugin before OpenNOW had applied its default saw the core
turn it back on at the next start. Writing the marker with the choice keeps the
core contract unchanged and makes the plugin's setting stick.

Verified with python3 -m unittest discover -s tests -t . (82 tests) and by
feeding the written file to the core under Deck DMI, which now keeps
identifyAsSteamDeck false instead of re-applying its default.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…11 (#1262)

* fix(streamer): send the native mic as plain Opus on the bundle's PT 111

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(streamer): send the native mic as plain Opus on the bundle's PT 111

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…1263)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…t sessions start (#1265)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…controls (#1264)

* fix(input): surface hidraw permission failures for wheels and flight controls

Refs #1258

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(input): drain HID diagnostics through the poll batch so startup messages are not lost

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…#1266)

Replace the Raspberry Pi FFmpeg fork with FFmpeg 9.0 plus the V4L2 Request
hwaccel patches so mainline stateless decoders (Rockchip Hantro/rkvdec/VPU981,
Allwinner, Raspberry Pi) are detected and used.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…lock dialog (#1267)

* fix(auth): keep the clock-skew refusal after a provider refresh

The auth.session.get reply that follows a provider refresh recomputed authState
from the session alone, so the refusal the user was reading turned back into
"idle" and vanished from the sign-in screen at every size. Settle it instead: a
signed-in result always wins, and an existing refusal stands until the next
attempt or a deliberate reset. Logout still resets explicitly.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(qt): show the clock warning while a signed-out launch is pending

The dialog gated on updaterSessionSafe, so a pending --launch-app-id launch --
the path that lands a signed-out user on sign-in -- closed the gate and the
warning never appeared. Split the gate: sessionWorkIdle keeps the session-work
half, the updater waits for the pending launch on top of it, and the clock
warning takes the session-work half alone.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(qt): give the clock dialog the keyboard so Escape and Return close it

A modal only receives Escape once an item inside it holds the focus, and the
sign-in screen behind it claims the focus while its primary button finishes
loading, so the dialog takes the keyboard when it opens and takes it back when
it loses it. The Basic style's button does not activate on Return, so the dialog
accepts that key itself.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…de decorations (#1268)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
zortos293 and others added 2 commits October 28, 2026 11:06
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(windows): suspend capture through the native window move loop

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(windows): run hidden HWND move-loop regression in CI

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(windows): retain native stream-item move-loop coverage

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(windows): retain row-level native move-loop CI results

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capy found 1 potential issue (1 medium).

View 3 other findings in Capy.

Open in Capy

* test(windows): execute the native stream-item move-loop probe in CI

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(qt): use the supported focus-window injection API

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capy found no issues.

Open in Capy

… node (#1277)

* fix(input): show NO HID PATH for allowlisted devices without a hidraw node

Refs #1258

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(input): wait for HID discovery after hotplug before reporting NO HID PATH

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(input): include <utility> for std::exchange in ControllerInput

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capy found 1 potential issue (1 medium).

Open in Capy

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capy found no issues.

View 1 other finding in Capy.

Open in Capy

* fix(qt): bound stream presentation and enable negotiated Cloud G-Sync pacing

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(qt): report Cloud G-Sync eligibility without claiming scanout

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(qt): deploy native presentation test runtime on macOS

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(qt): run presentation policy suites in PR checks

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(streaming): preserve and pace sender-timed GPU frames

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ci: allow manual CodeQL validation on test branches

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capy found no issues.

View 3 other findings in Capy.

Open in Capy

…1366)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capy found no issues.

Open in Capy

Alexis-D-ff and others added 2 commits October 29, 2026 20:05
…nly (#1281)

The catalog support gate dropped in-game settings persistence for titles
whose variants do not advertise IN_GAME_SETTINGS_PERSISTENCE_ENABLED
(Diablo IV, appId 102348311), sending enablePersistingInGameSettings:
false in every session request while the official Bifrost client sends
the toggle value unconditionally. Settings then reset between sessions
on affected titles even for entitled members with the toggle enabled.

Honor the user toggle only, matching the official client, and update the
unit test to the new semantics.

Fixes #1280

Co-authored-by: Cursor <cursoragent@cursor.com>
…detected (#1301)

Mesa radeonsi rejects H.264 decode contexts below 64x64 with
VA_STATUS_ERROR_RESOLUTION_NOT_SUPPORTED. The vendored cros-codecs
VaapiBackend and OpenNOW's capability probe both created a 16x16
placeholder context, so on AMD iGPUs (verified on Renoir, Mesa 26.2.5)
the vaapi backend was reported unavailable ('VA-API cannot create the
initial H.264 decoder context: resolution not supported') and 'auto'
ended in streamer_codec_unavailable.

The placeholder is replaced by new_sequence once the SPS arrives, so use
a shared INITIAL_CONTEXT_SIZE of 64.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capy found no issues.

Open in Capy

MONZikWasTaken and others added 9 commits November 3, 2026 22:37
…m toggle and pacer fixes (#1300)

* feat(qt): frame generation levels, capability test, target FPS and display refresh

Frame generation grows from a single 2x switch into a measured feature set:

- Levels off/2x/3x/4x (`frameGeneration`), a target output rate
  (`frameGenerationTargetFps`, 0 = follow the level; the level is derived
  as round(target / stream fps)) and a display refresh override
  (`frameGenerationDisplayHz`, 0 = the screen OpenNOW is on). The core
  validates all of them plus a bounded `frameGenerationProfiles` map.
- StreamFramePacer schedules N-1 generated frames per source interval
  (`generated()` / `takeNext()`), the display gate scales with the factor
  and the fixed 120 FPS ceiling is replaced by a 4 ms source floor. The
  interpolator contract reports how many frames it can synthesise
  (`generatedCount()` / `generatedTexture()`); the block-matching engine
  stays midpoint-only, so 3x/4x need the FG-Legacy engine.
- FrameGenerationBenchmark runs the engine offscreen on a private QRhi
  at the selected stream resolution with procedural frames and reports
  p50/p95 per generated frame (GPU timestamps when the backend delivers
  them, wall time around the blocking offscreen frame otherwise);
  `evaluate()` allows a level when p95 * (N-1) fits 60 % of a source
  interval, the display shows fps * N and the engine supports N.
  Results are saved per preset (engine|gpu|WxH|fps). Levels and targets
  stay locked in Settings until a passing profile exists; a selection the
  test rules out resets to Off. SettingsState owns the logic; desktop and
  console screens share it.
- Settings rows: level segments, Run test, a GitHub link to the Framegen
  repository, Target FPS and Display refresh; stats badge FRAME GEN Nx;
  "Source rate too high" replaces the 120 FPS limit label.
- FG-Legacy attribution: third_party/framegen notices and a
  THIRD_PARTY_NOTICES section; docs/fg-legacy.md describes the design.

Tests: framepacer (N-x schedule, display scaling, floor), a new
framegenerationbenchmark test (decision rules + a real measurement),
nativeframegeneration/streamvideo/frameinterpolator/localization, the
frame-generation QML acceptance (locked until tested, unlocked by a
profile) and the Rust settings tests. QML smoke tests that open the
stream route only run with a visible window on Windows.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(qt): frame generation mode selector; capability test gates the feature

- One mode control instead of three parallel settings: Off / Multiplier /
  Target FPS / Display (`frameGenerationMode`). Only the control the mode
  needs is shown: multiplier chips, or the output rates the display can
  show at 2x..4x the stream rate. Display mode takes the largest allowed
  multiplier for the detected refresh rate. Profiles saved before the
  mode existed with a level set read as multiplier mode.
- The capability test is its own row (status + Run test + Framegen on
  GitHub). Until it has passed for the current preset the feature is
  unavailable everywhere: mode controls are disabled and the stream
  surfaces ignore a stored level (`frameGenerationFactor()` is 0 unless
  the profile allows the requested factor). The stats acceptance seeds a
  passing profile; the QML acceptance checks the locked and unlocked
  states.
- Display refresh override moves under Advanced (Auto shows the
  detected rate); the first-run Boost hint mentions the test.
- Measurement falls back to wall time around the blocking offscreen
  frame when the backend reports no GPU timestamps (D3D11 does for
  offscreen frames); `gpuTimed` reflects it. New
  opennow-framegenerationbenchmark-tests covers the decision rules and a
  real measurement.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(qt): decide frame-generation levels live and name the real reason

The capability test measured the GPU correctly but the verdict was frozen
at test time and every empty result read "Too slow", even when the display
was the limit. Profiles now store the measurement only, keyed by engine,
GPU and resolution (the cost does not depend on the frame rate); which
levels fit is recomputed from the selected frame rate and the current
display whenever either changes. The status names the actual reason:
the display cannot show fps x 2, the GPU exceeds the budget, or the engine
is midpoint-only; chip hints follow the same reasons. Measurements saved
with the old fps-suffixed key remain valid.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(qt): frame generation capability dialog with the full rate x level table

Run test now opens a dialog that shows every option at once instead of one verdict for the current preset: the detected display rate with its override, the stream rate chips, the mode chips, and a table with one row per stream rate and one cell per level (2x, 3x, 4x). Each cell shows the output rate and a check mark or the reason it does not fit (display, GPU, engine); greyed cells explain the numbers on hover. Clicking a row picks the stream rate, clicking a cell the level or target, and Apply writes fps, mode and level together. The GPU is measured once when no profile exists.

SettingsState gains frameGenerationVerdictFor(fps) and frameGenerationMatrix(); --smoke-frame-generation-dialog seeds a 2 ms / 240 Hz measurement and opens the dialog for screenshots.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(qt): frame generation in the stream menu and on a shortcut; tolerant pacer; persisted choices

Ctrl+G gains a Frame generation tile while the feature is in use (on, or switched off from the menu this session); switching it off remembers mode, level and target in memory and switching it on restores them. The same toggle is bound to shortcutToggleFrameGeneration (default Ctrl+Shift+G) and reports through the stream control message. Frame generation choices now go through setSetting, so they persist across restarts (they were only applied to the local settings map before).

The presenter acquires the latest decoded frame, so a burst skips one source frame: StreamFramePacer now treats a single skipped frame as continuous once the cadence is known (per-frame deltas feed the cadence estimate) and only starts the two-second overload cooldown after four collisions within a second; a single early arrival just drops the stale schedule. The stats overlay names the reason behind a Discontinuity.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(qt): block-matching engine generates 3x and 4x; capability test times every level

The synthesize pass takes t: the previous frame travels t of its forward flow and the current frame (1 - t) of its backward flow, blended 1 - t : t, so one pair yields N - 1 frames at t = k / N. Each generated frame has its own render target and its own uniform buffer per slot, so the passes of one pair never overwrite each other's t inside a command buffer. MaximumFactor is 4; midpointTexture() keeps returning the frame nearest t = 0.5.

The capability test now times 2x, 3x and 4x in turn (one second each) and stores p95Ms, p95Ms3 and p95Ms4; evaluate() uses the measured per-level cost and falls back to p95 x (N - 1) for profiles from before. The engine maximum comes from the installed engine, not the stored profile, so an earlier measurement no longer caps the levels. On the RTX 4060 Ti the synthesis pass is nearly free next to the flow (0.23 / 0.27 / 0.23 ms per source frame at 640x360).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(qt): presenter keeps every frame of a 3x or 4x pair selectable

StreamVideoTextureRenderer held three external texture slots (two history frames and the midpoint), so the second generated frame of a 3x or 4x pair failed selectTexture() and generation switched itself off with 'unavailable'. Five slots now cover the two history frames plus up to three generated frames. New tst_nativeframegeneration case drains 4x pairs through the production callback and asserts the status never drops to unavailable.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(qt): steady frame-generation pacing on bursty streams and high factors

The display gate used a stricter tolerance than the capability test and judged it on a single frame's timestamp delta, so 60 FPS x 4 on a 240 Hz panel flipped between active and display-refresh with every jittery frame. The pacer now smooths the source interval with the median of the last eight per-frame timestamp deltas (arrival deltas as the fallback) and applies the test's 0.975 tolerance.

A source frame that lands on an unfinished pair is a burst, not overload: the stale schedule is dropped and the newest pair wins. Overload is reserved for a stalled presenter, which the render callback reports when nothing new has been swapped for max(100 ms, 4 x interval). Every frame-generation status change is written to qt-native.log with the pacer's numbers, throttled to five lines a second, so flicker can be diagnosed from a real session.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(qt): name the paused frame-generation state for what it is

The pacer's overload state means the display stopped taking frames, not GPU load; the stats overlay now says so instead of 'Overloaded'.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(qt): lower the factor instead of stopping when the display is slightly short; log GUI stalls

A live '60 FPS' cloud stream ran at 61.7 FPS, so 4x needed ~247 Hz and the 240 Hz panel kept flipping generation into display-refresh. The display gate now tolerates 5 % overshoot and, when the display is still too slow, lowers the factor for the pair (the render callback ingests and schedules the pacer's effective factor); only when 2x does not fit is generation off.

The live log also showed a three-frame sequence gap every second. Frames are requested from the GUI thread, so StreamVideoItem now runs a 10 ms GUI-thread probe and logs stalls of 35 ms or more to qt-native.log. A wall-clock sensitive native test step was tightened so its arrival cadence stays inside the pacer's window.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(qt): Experimental badge and engine notice for frame generation; live capture diagnostic

Settings rows can carry a status pill; frame generation shows an Experimental badge and, on desktop, console and in the capability dialog, a notice that the current engine was not designed for games and shows strong artifacts on thin edges and fast motion until the FlyHigh model replaces it.

Diagnostics: OPENNOW_FG_CAPTURE_DIR makes the render callback save the pair and every generated frame as PNG a few times per session, and OPENNOW_FG_DUMP runs the engine on two image files in tst_frameinterpolator (skipped otherwise).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* style(core): rustfmt frame generation settings tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(qt): preserve benchmark device ownership and profile validity

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(qt): align recovery fixture with sender cadence

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(streaming): preserve skipped-pair span and reject raw arrival gaps

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(windows): protect frame-generation minimum calls from macros

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(framegen): persist benchmark acceptance results in CI

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: zortos293 <65777760+zortos293@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(network): measure region latency with a bounded HTTPS round trip

VPN tunnels make TCP connect RTT lie. Time an HTTPS HEAD on a
keep-alive connection instead, with redirects disabled, DNS pinned to
pre-resolved addresses so Client drop cannot outlive the timeout, and
fresh-TLS outliers retried then dropped.

Co-authored-by: Cursor <cursoragent@cursor.com>

* style(core): format HTTPS region measurements

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(network): bound region measurement DNS resolution

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(network): normalize accepted socket blocking mode

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(network): exclude fresh connections from region RTT samples

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(network): route region pings through configured or system proxies

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: zortos293 <65777760+zortos293@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…1367)

* fix(session): announce real display DPI instead of hardcoded Mac 144

Compute physical DPI as (logical size × devicePixelRatio) / panel mm and
send it in clientRequestMonitorSettings so remote Windows UI scaling
matches Retina and other HiDPI displays. Also expose GFN's Mac 3.5K
(3456x2160) preset in the 16:10 catalog.

Co-authored-by: Cursor <cursoragent@cursor.com>

* style(core): format monitor DPI fallback

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: zortos293 <65777760+zortos293@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…#1405)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(webrtc): align native signaling with the v3 wire contract

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(test): normalize accepted signaling socket mode

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(settings): add seven shared resolution presets

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(settings): synchronize native resolution fallback catalog

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capy found no issues.

View 3 other findings in Capy.

Open in Capy

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capy found no issues.

View 2 other findings in Capy.

Open in Capy

This branch was successfully deployed

1 active (outdated) deployment
qt-update-signing — b3fe7102 Deployed Oct 27, 2026 by capy-ai[bot] via Isolated nightly update signing #1515
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants