Skip to content

TargetBridge 3.6.0: resilient receivers, adaptive streaming, and permission guidance - #206

Merged
swellweb merged 20 commits into
dev-3.6.0from
codex/dev-3.6-foundation
Oct 12, 2026
Merged

swellweb merged 20 commits into
dev-3.6.0from
codex/dev-3.6-foundation

Conversation

@swellweb

@swellweb swellweb commented Oct 3, 2026

Copy link
Copy Markdown
Owner

Summary

  • remember discovered receivers by stable identity instead of transient IP
  • add per-receiver Automatic, H.264, and HEVC codec selection with hardware fallback
  • add Full HD and Full HD 60 native-scale profiles for non-Retina 1080p receivers
  • capture the virtual display directly in genuinely headless mirror sessions while retaining Audio Relay
  • add an in-app Sender permission assistant for Screen Recording, Accessibility, and Input Monitoring
  • prepare Sender and Receiver as version 3.6.0 with release notes and documentation

Validation

  • xcodebuild test: 163 Sender tests passed
  • make test: 654 Receiver checks passed
  • stable receiver identity package passed
  • localization validation passed for de, en, fr, it, zh
  • Sender Release build is universal (arm64/x86_64), versioned 3.6.0 (20261003023000), and codesign verification passed
  • Receiver Release build is arm64, versioned 3.6.0 (20261003023000), bundled without external Homebrew references, and codesign verification passed
  • local Receiver loopback smoke passed: handshake, corrupt packet rejection, idle watchdog, H.264 decode, and rendering

Scope

This targets dev-3.6.0 only. It does not merge into main and does not include the experimental lossless 5K60 or Metal-renderer work.

Betafer and others added 9 commits October 3, 2026 02:21
The receiver advertises a fixed 5120x2880 panel with a 2560x1440 HiDPI mode and
`"hiDPI":true`, regardless of the display it is actually attached to. The sender
took those values as the virtual display geometry, so a 1080p stream was captured
from a 1440p HiDPI desktop and rescaled twice on the way to a 1080p panel: the
desktop arrived at the wrong size and paid for two non-integer resamples.

Render matching could not fix this either. It sizes the backing store to the
stream but keeps the strictly 2x HiDPI contract, so 1080p became a 960x540
desktop — capture is 1:1, but every control is drawn at 2x on a panel with no
extra pixels to resolve it.

Give `TBVirtualDisplayModeSize` an explicit `isHiDPI`, defaulting to the existing
2x behaviour, and let the capture profile choose. The 1080p profiles declare
native scale and get a 1920x1080 1x mode; every other profile is untouched and
still derives its mode and HiDPI flag from the receiver profile.

The trigger is the profile rather than the receiver's reported panel size,
because that value is a compile-time constant on the receiver and cannot
distinguish a 1080p panel from a 5K one.

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

estuo commented Oct 5, 2026

Copy link
Copy Markdown

Thanks for the work on 3.6! One thing worth checking before merging: at the current head (5f38362), the three fixes from merged PR #205 appear to be missing:

  • 9ea26f5: cached CGEventSource and asynchronous/sampled input logging. This head still creates a new event source and performs synchronous file logging on the main actor for incoming mouse events.
  • b6f5424: backing-pixel checks when selecting the Render Matching display mode. preferredMode() still matches logical dimensions and refresh rate only.
  • 3a74aad: NSLocalNetworkUsageDescription and NSBonjourServices (_targetbridge._tcp) in the Sender Info.plist.

Could these fixes be carried forward into 3.6 before release? The display fix may need adapting to the new resolvedMode.isHiDPI / native-scale logic rather than applying the old patch unchanged.

This is based on source inspection; I haven’t tested a 3.6 build. I’m flagging it to help preserve the fixes already merged in #205.

@estuo

estuo commented Oct 6, 2026

Copy link
Copy Markdown

A few questions come to mind from the source at 10f8ccd that might be worth checking before release:

  1. Existing display profiles and arrangements after upgrading. The display-profile lookup uses id:, which changes when an older | selection is matched to receiver:. The Extended Desktop arrangement key also appears to change from a service: discriminator to receiver:. Is there a legacy lookup or migration elsewhere that preserves those settings? If not, carrying them forward when the identities are matched could help, without overwriting settings already saved under the new keys.
  2. Per-receiver codec preference. In TBDisplaySenderManager.swift, the session-based save key appears to become service:receiver:, whereas the receiver-based restore key remains service:. Could that mismatch make an explicitly selected H.264 or HEVC preference revert to Automatic when the receiver is selected again?
  3. Release workflow conditionals. Both certificate-import steps in .github/workflows/release.yml use secrets.TARGETBRIDGE_CODESIGN_P12_BASE64 directly in if:. GitHub’s documentation says secrets cannot be referenced directly in these conditionals and suggests using a job-level environment variable instead. Could this prevent the tag-triggered release workflow from starting, even though the regular PR CI passes?

I may be missing some surrounding handling, and I haven’t reproduced the application behaviour in a 3.6 build. These are just possible edge cases suggested by the current source and workflow configuration.

@swellweb
swellweb merged commit 8fe9c6a into dev-3.6.0 Oct 12, 2026
4 checks passed
@estuo

estuo commented Oct 12, 2026

Copy link
Copy Markdown

Thanks for merging #206 and carrying forward the three fixes from #205!

I took another look at the merged dev-3.6.0 head (8fe9c6a) and noticed two possible persistence regressions worth checking before the 3.6 release.

1. Per-receiver codec preference keys do not appear to round-trip.

In TBDisplaySenderManager.swift, receiverCodecPreferenceKey(for session:) derives its key from selectedReceiverID. With the new stable identity (receiver:<UUID>), this produces service:receiver:<UUID>.

However, receiverCodecPreferenceKey(for receiver:) looks up service:<Bonjour service name>.

Those keys do not match. Selecting a receiver again calls restoreCodecPreference(..., defaultToAutomatic: true), which appears to reset an explicitly selected HEVC/H.264 preference to Automatic.

One possible reproduction would be: select a discovered receiver → choose HEVC → switch to Manual receiver entry → select the same receiver again.

Using the stable receiver UUID consistently for both saving and restoring, with a legacy service-name fallback, might resolve this.

2. Saved display settings may need migration to the UUID identity.

matchesPersistedIdentity() handles older receiver selections, but several other persisted settings seem to use the new identity without a corresponding legacy lookup:

  • receiverProfileKey for saved display profiles
  • extendedArrangementDefaultsKey for Extended Desktop placement
  • TBVirtualDisplayModeMemory.preferenceKey for manually selected HiDPI modes

In 3.5.2 these keys were derived from the previous service/picker identity. With 3.6 they can change to receiver:<UUID>, leaving existing records inaccessible after an upgrade.

A one-time legacy lookup and copy, only when no setting exists under the new key, might preserve existing layouts and resolution preferences.

These observations are based on source inspection only; I have not tested a 3.6 build on hardware. I may have overlooked some migration handling elsewhere.

Would these two cases be worth covering with regression tests before tagging 3.6.0?

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.

4 participants