I've encountered a bug in macOS 14 while using ScreenCaptureKit. I've filed a Feedback (FB13561921), but in the meantime, I'm wondering if anyone has a way to circumvent this bug.
The bug
When using ScreenCaptureKit to get frames from a captured display, the frames become incorrect/unreliable after using the “Move focus to next window” (CMD `) keyboard command in an app with multiple open windows. The foregrounded window doesn’t appear foregrounded — it still appears behind the last focused window.
In other words, I expect to see the first image (and do on the actual screen), but see the 2nd on the captured screen.
Steps to reproduce:
- Run Apple’s sample app from https://developer.apple.com/documentation/screencapturekit/capturing_screen_content_in_macos
- Note: I know that in most cases, I should include a minimal reproducible example inside the question. However, the amount of code required to setup a SCK session and display the output is non-trivial. Plus, using Apple's own code demonstrates that this isn't a misuse of the framework by this programmer.
- Capture the full display (not a single window)
- Open two windows in Text Edit and place the two so that they overlap partially. You should see this reflected in CaptureSample
- Use CMD ` to switch between the open windows of Text Edit.
- At this point, on your actual display, you can see the Z Index of the Text Edit windows change, but in CaptureSample, you will see that the Z Index change is not reflected -- the previously-focused window still remains on top.
- Once you use the mouse to move one of the windows (or if you just click in between the windows), you will see the frames start to update again
This bug happens on macOS 14.1-14.3. It does not appear to happen on macOS 13.6
Attempts I've made to work around this
- Starting and stopping the
SCStreamdoesn't seem to have any effect -- the restarted stream still has the previously-focused window on top - I can use Accessibility commands to mimic the "fix" of dragging the window to a new position (and then back), but this creates visual artifacts.
The Question
Is there a workaround to get ScreenCaptureKit to produce valid updated frames of the newly-focused window after this key command has been used that doesn't create visual artifacts or require Accessibility like my "hack"?
Code for my Accessibility "hack" that isn't really workable due to visual artifacts:
func moveTheWindow(_ point: CGPoint) async {
guard let originalCursorPosition = CGEvent(source: nil)?.location else {
print("Failed to get the original cursor position.")
return
}
let mouseDownPoint = point
let mouseDragRightPoint = CGPoint(x: point.x + 1, y: point.y)
let mouseDragLeftPoint = CGPoint(x: point.x, y: point.y)
// Mouse down at the starting point
if let mouseDown = CGEvent(mouseEventSource: nil, mouseType: .leftMouseDown, mouseCursorPosition: mouseDownPoint, mouseButton: .left) {
mouseDown.post(tap: .cghidEventTap)
}
// Drag the window 1px to the right
if let mouseDragRight = CGEvent(
mouseEventSource: nil,
mouseType: .leftMouseDragged,
mouseCursorPosition: mouseDragRightPoint,
mouseButton: .left
) {
mouseDragRight.post(tap: .cghidEventTap)
}
// Mouse up to finish the first drag
if let mouseUpRight = CGEvent(
mouseEventSource: nil,
mouseType: .leftMouseUp,
mouseCursorPosition: mouseDragRightPoint,
mouseButton: .left
) {
mouseUpRight.post(tap: .cghidEventTap)
}
try? await Task.sleep(nanoseconds: NSEC_PER_SEC / 20)
// Mouse down again for the second drag
if let mouseDownAgain = CGEvent(
mouseEventSource: nil,
mouseType: .leftMouseDown,
mouseCursorPosition: mouseDragRightPoint,
mouseButton: .left
) {
mouseDownAgain.post(tap: .cghidEventTap)
}
// Drag the window back 1px to the left
if let mouseDragLeft = CGEvent(
mouseEventSource: nil,
mouseType: .leftMouseDragged,
mouseCursorPosition: mouseDragLeftPoint,
mouseButton: .left
) {
mouseDragLeft.post(tap: .cghidEventTap)
}
// Mouse up to complete the second drag
if let mouseUpLeft = CGEvent(mouseEventSource: nil, mouseType: .leftMouseUp, mouseCursorPosition: mouseDragLeftPoint,
mouseButton: .left)
{
mouseUpLeft.post(tap: .cghidEventTap)
}
try? await Task.sleep(nanoseconds: NSEC_PER_SEC / 20)
// Move the cursor back to the original position
CGWarpMouseCursorPosition(originalCursorPosition)
}

