Skip to content

Sender: GUI options for screensaver/wake handling, stable extended-display identity, diagnostic logging#69

Closed
DrDavidL wants to merge 2 commits into
swellweb:mainfrom
DrDavidL:codex/screensaver-resilience
Closed

Sender: GUI options for screensaver/wake handling, stable extended-display identity, diagnostic logging#69
DrDavidL wants to merge 2 commits into
swellweb:mainfrom
DrDavidL:codex/screensaver-resilience

Conversation

@DrDavidL
Copy link
Copy Markdown
Contributor

@DrDavidL DrDavidL commented May 26, 2026

Summary

Sender-side GUI options that address two separate observed issues — windows on the extended display being forgotten after each reconnect, and intermittent post-event stutter — plus diagnostic plumbing for a longer-duration stutter that is still being characterized.

After in-field testing of the original screensaver-focused commit, both screensaver-related toggles default to off in this update — they didn't fix the long-duration stutter reproduced by the user and the actual repro cause appears to be unrelated to display sleep. They remain available as options for setups where they help.

Changes

Stable extended-display identity (ReceiverBackedVirtualDisplaySession.swift)

  • The extended-desktop virtual display previously generated a random productID/serialNumber on every extendedDesktop() call. macOS keys window-placement memory on those fields, so every reconnect looked like a brand-new display and windows on it were not restored.
  • Identity is now derived deterministically (djb2) from profile.receiverName + panelWidth + panelHeight — same fields the codebase already uses for the persisted extended-arrangement key. Same receiver → same virtual display identity across reconnects → window placement persists.
  • TBDisplayCaptureSource.virtualDisplayIdentity now takes a TBMonitorDisplayProfile.

Settings card toggles (TBDisplaySenderContentView.swift, TBDisplaySenderManager.swift, TBDisplaySenderService.swift)

  1. Prevent screensaver / display sleep while streaming — default off. When on, adds .idleDisplaySleepDisabled to ProcessInfo.beginActivity so the screensaver cannot engage during a session.
  2. Auto-restart capture after wake / unlock — default off. When on, observes screensDidWakeNotification, com.apple.screenIsUnlocked, and com.apple.screensaver.didstop, and soft-restarts the capture pipeline (SCStream/CGDisplayStream + VTCompressionSession + capture timers + activity) while preserving the NWConnection and the virtual display. PTS sequence is reset to avoid receiver-side pacing desync.
  3. Log virtual display events to Console (verbose) — default off. When on, registers CGDisplayRegisterReconfigurationCallback to log every add/remove/mirror/mode-change event affecting any display, and emits a 60 s stream snapshot to NSLog containing streaming state, FPS, virtual display online status, pending packet count, in-flight encode count, and PTS sequence. Intended for diagnosing the long-duration stutter from Console.app without leaving heavy logging on for ordinary users.

Per-session button

  • Restart capture (enabled while streaming) — runs the same soft-restart path on demand. Notably this does not rebuild the virtual display or NWConnection; the long-duration stutter so far does not appear to be cleared by this and remains under investigation (see the verbose logging toggle).

UserDefaults keys: fd.tbdisplaysender.preventDisplaySleep, fd.tbdisplaysender.autoRestartOnWake, fd.tbdisplaysender.verboseDisplayLogging. Existing stored values are preserved across the default flip.

Localized in IT / EN / DE.

Test plan

  • Build via Xcode (TBDisplaySender scheme) — verified locally, builds clean.
  • Default install (all three toggles off) behaves identically to current main.
  • With extended-desktop capture mode and window placement set up on a receiver, disconnect → reconnect — windows return to the extended display rather than reflowing to the laptop. Test with at least two distinct receivers to confirm identities don't collide.
  • Enable "Prevent screensaver / display sleep" + let the Mac idle past the screensaver timeout while streaming — screensaver should not engage.
  • Enable "Auto-restart capture after wake" only + manually trigger a screensaver and dismiss it — soft-restart logs should fire (TargetBridge: system wake — soft restart of capture pipeline).
  • Click "Restart capture" while streaming — capture briefly drops and reestablishes; NWConnection and virtual display remain.
  • Enable "Log virtual display events to Console (verbose)" and stream — Console.app shows reconfiguration events and the 60 s snapshot lines.
  • All three settings persist across app relaunch.

🤖 Generated with Claude Code

Adds three user-controllable options in the sender GUI to avoid the
"stutter that needs an app restart" symptom after the screensaver or
display sleep activates while streaming.

- Settings toggle "Prevent screensaver / display sleep while streaming"
  (default on, persisted) gates passing `.idleDisplaySleepDisabled` to
  `ProcessInfo.beginActivity`, so the screensaver/display-sleep does
  not engage during an active session.
- Settings toggle "Auto-restart capture after wake / unlock" (default
  on, persisted) enables observers on `screensDidWake`,
  `com.apple.screenIsUnlocked`, and `com.apple.screensaver.didstop`.
  When fired, the capture pipeline (SCStream/CGDisplayStream +
  VTCompressionSession + capture timers + activity) is torn down and
  rebuilt while keeping the network connection and the virtual
  display alive. The PTS sequence counter is reset so the receiver's
  pacing does not desync after the gap.
- Per-session "Restart capture" button (enabled while streaming) runs
  the same soft-restart path on demand for any other stutter cause.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@swellweb
Copy link
Copy Markdown
Owner

Thank @DrDavidL I put the Pr in the new release.

…t toggles off

After field testing, the prior toggles did not address the long-running
stutter (which is duration-driven, not screensaver-driven). Updates:

- Default both "Prevent screensaver / display sleep" and "Auto-restart
  capture after wake" to OFF. Both remain available for users who find
  them useful; existing UserDefaults values are preserved.

- Derive the extended-desktop virtual display productID/serialNumber
  deterministically from the receiver identity (name + panel dimensions)
  instead of randomizing each session. macOS keys window-position memory
  on these fields, so the random identity caused windows on the
  extended display to be forgotten on every reconnect. Same receiver
  now produces the same virtual display identity across reconnects,
  which lets the system restore prior window placement and matches the
  identity convention already used for the saved arrangement key.

- Add an opt-in "Log virtual display events to Console (verbose)"
  toggle (default off). When enabled, registers a
  CGDisplayRegisterReconfigurationCallback that logs every add /
  remove / mirror / mode-change event affecting any display, and
  emits a periodic (60 s) stream snapshot to NSLog with streaming
  state, FPS, virtual display online status, pending packet count,
  in-flight encode count, and PTS sequence. Intended for diagnosing
  the long-duration stutter from Console.app without leaving heavy
  logging on for ordinary users.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@DrDavidL DrDavidL changed the title Sender: avoid post-screensaver stutter (display-sleep prevention + wake auto-restart + manual restart) Sender: GUI options for screensaver/wake handling, stable extended-display identity, diagnostic logging May 27, 2026
@DrDavidL
Copy link
Copy Markdown
Contributor Author

Superseded by #82, which contains these commits (4f6fd8f, 46b2c79) plus the rest of the long-session-stutter fix. Collapsing the stack into #82 so it lands as a single PR.

@DrDavidL DrDavidL closed this May 30, 2026
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.

2 participants