Skip to content

fix: restore cursor and SCStream capture after display sleep/wake#42

Open
cwy433-png wants to merge 1 commit into
tranvuongquocdat:mainfrom
cwy433-png:fix/cursor-after-wake
Open

fix: restore cursor and SCStream capture after display sleep/wake#42
cwy433-png wants to merge 1 commit into
tranvuongquocdat:mainfrom
cwy433-png:fix/cursor-after-wake

Conversation

@cwy433-png

Copy link
Copy Markdown

Problem

After the Mac's displays sleep and wake again, the mouse cursor is no longer
visible on the tablet even though the pointer has actually moved onto the
virtual display (clicks still land). The cursor stays lost for the rest of
the session.

Root cause (two stacked issues, confirmed via /tmp/sidescreen.log)

  1. When the displays go to sleep, SCStream dies with
    SCStreamErrorDomain -3815 ("no displays or windows to capture"). The
    stream-error delegate then silently switches capture to the
    CGDisplayStream fallback — and the session stays on the fallback
    forever, even after wake.
  2. The fallback was created with properties: nil, and
    kCGDisplayStreamShowCursor defaults to false, so the fallback never
    composites the cursor at all.

Net effect: any sleep/wake cycle permanently drops the session onto a
capture path that cannot show the pointer.

Fix

  • ScreenCapture registers NSWorkspace.screensDidWakeNotification /
    didWakeNotification observers while a capture is active (removed in
    stopStreaming(), with a defensive deinit backstop). A full system wake
    fires both notifications, so they are coalesced into a single restart.
  • On wake, after a 2 s settle delay: if the fallback is active, stop it and
    retry SCStream via the existing restartStream() path (which re-enters
    the fallback on its own if SCStream still cannot start). The
    wake-triggered restart does not consume the frame monitor's one-shot
    stall-recovery budget.
  • The CGDisplayStream fallback now sets CGDisplayStream.showCursor, so
    the pointer remains visible even while the fallback is in use.

One file changed (MacHost/Sources/ScreenCapture.swift), no new
dependencies, no private API.

Testing

M1 MacBook Air, macOS 15.7.5 (24G624), Android tablet over USB, HEVC,
1280x800 HiDPI. Each scenario was verified twice (before final cleanup and
on the final patch):

  1. Display sleep (no password on wake) → wake: cursor visible and persists.
  2. Display sleep (password required) → unlock: cursor visible and persists.
  3. Full system sleep via power button → wake: cursor visible and persists.

Log-verified sequence on every cycle:
SCStream stopped with error … -3815CGDisplayStream fallback started
(cursor now still visible thanks to showCursor) → wake → Wake restart: leaving CGDisplayStream fallback, retrying SCStreamFirst frame received from SCStream (restart).

Also verified: repeated sleep/wake cycles do not stack observers or
restarts, and stopping the server unregisters the observers.

Notes — approaches tried and rejected (for reviewer context)

  • Private cursor-scale nudge (CGSSetCursorScale, the programmatic
    "shake to locate"): rejected. Manual testing showed the enlarged cursor is
    visible on the tablet but the pointer disappears again the moment the
    scale returns to normal, so a transient nudge cannot fix the steady state.
    Dropping it also avoids introducing a new private API.
  • CGDisplayHideCursor/CGDisplayShowCursor toggle after wake: was
    implemented behind a debug switch during testing and turned out to be
    unnecessary once the fallback/showCursor issue was fixed; removed from the
    final patch.
  • Restarting the capture stream alone (without the fallback showCursor
    fix) is not sufficient — by the time the machine wakes, capture has
    already degraded to the cursor-less fallback, and a session that ever
    falls back for other reasons would still lose the pointer.
  • There are reports of an OS-level cursor-compositing bug on virtual
    displays after wake (e.g. BetterDisplay discussions #999 / #2770). That
    may still exist independently on some macOS versions, but in SideScreen's
    case the dominant cause was the two issues above: on macOS 15.7.5 the
    restarted SCStream composites the cursor correctly after every wake.

@vercel

vercel Bot commented Jul 9, 2026

Copy link
Copy Markdown

@cwy433-png is attempting to deploy a commit to the tranvuongquocdat's projects Team on Vercel.

A member of the Team first needs to authorize it.

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.

1 participant