How Vorssaint Captures Window Thumbnails Using Screen Recording Permission on macOS

Vorssaint captures window thumbnails by checking macOS Screen Recording permission before invoking ScreenCaptureKit APIs to enumerate windows and render high-resolution captures, falling back to app icons when permission is denied.

The WindowPreviewProvider service in the vorssaint/vorssaint-utils repository handles all thumbnail generation for the Vorssaint window switcher. When the app detects that Screen Recording permission is granted via Permissions.shared.screenRecording, it uses the public ScreenCaptureKit framework to capture live window images. Without this permission, the system aborts the capture attempt and the UI displays application icons instead.

Permission Gating Before Capture

Every thumbnail refresh begins with a strict permission check to ensure the app respects user privacy settings. In Sources/Vorssaint/Services/Switcher/WindowPreviewProvider.swift, the refreshPreviews method validates the screenRecording flag before proceeding.

If permission is missing or capture is paused, the operation cancels immediately:

guard Permissions.shared.screenRecording, !Self.captureIsPaused else {
    cancel()
    return
}

This guard clause appears at lines 67‑70 and prevents any unauthorized access to screen content.

Enumerating Windows with ScreenCaptureKit

Once permission is confirmed, the provider builds a shareable content object to discover all available windows across spaces. The code creates an SCShareableContent instance that includes windows currently off-screen or on other desktops:

let content = try? await SCShareableContent.excludingDesktopWindows(
    false,
    onScreenWindowsOnly: false)

As seen at lines 154‑156, setting excludingDesktopWindows to false and onScreenWindowsOnly to false ensures the switcher can preview windows from any space, not just the currently visible ones.

Capturing High-Resolution Thumbnails

For each target window, Vorssaint constructs a content filter and stream configuration to capture pixel-perfect renders. The implementation at lines 179‑182 creates an SCContentFilter bound to a specific window, then calls SCScreenshotManager.captureImage to produce a CGImage:

let filter = SCContentFilter(desktopIndependentWindow: scWindow)
let image = try? await SCScreenshotManager.captureImage(
                contentFilter: filter,
                configuration: configuration)

The SCStreamConfiguration object defines the maximum pixel dimensions and disables the cursor to ensure clean window previews.

Post-Processing and Caching

Raw captures undergo processing to handle visual artifacts and optimize memory usage. The bitmap copy logic at lines 202‑209 downscales images to the requested maximum pixel size, while the rectification logic at lines 188‑199 corrects "strip" artifacts caused by Stage Manager layouts.

Processed images populate an in-memory cache and trigger UI updates via the onUpdate callback, ensuring the window switcher displays thumbnails without lag.

Fallback Behavior When Permission Is Denied

If Permissions.shared.screenRecording returns false, the refreshPreviews method returns immediately without attempting capture (see comment at lines 15‑16). The switcher UI then renders the application's icon instead of a live thumbnail, maintaining functionality while respecting the user's privacy choice.

You can manually trigger a single-window capture using this pattern:

func captureWindow(_ id: CGWindowID) async -> CGImage? {
    guard Permissions.shared.screenRecording else { return nil }
    guard let content = try? await SCShareableContent.excludingDesktopWindows(false,
                                                                            onScreenWindowsOnly: false) else { return nil }
    guard let scWindow = content.windows.first(where: { $0.windowID == id }) else { return nil }
    let filter = SCContentFilter(desktopIndependentWindow: scWindow)
    var config = SCStreamConfiguration()
    config.width = Int(scWindow.frame.width)
    config.height = Int(scWindow.frame.height)
    config.showsCursor = false
    return try? await SCScreenshotManager.captureImage(contentFilter: filter,
                                                       configuration: config)
}

Summary

  • Vorssaint requires explicit Screen Recording permission before attempting any thumbnail capture, enforced by a guard clause in WindowPreviewProvider.swift.
  • ScreenCaptureKit powers the capture pipeline, using SCShareableContent for window discovery and SCScreenshotManager.captureImage for rendering.
  • Post-processing handles Stage Manager artifacts and scales images to requested dimensions before caching.
  • Fallback to app icons ensures the switcher remains usable when permission is denied or when running on older macOS versions.

Frequently Asked Questions

What happens if Screen Recording permission is denied in Vorssaint?

The refreshPreviews method returns immediately without capturing content, and the UI displays the target application's icon rather than a live thumbnail. This behavior is defined in Sources/Vorssaint/Services/Switcher/WindowPreviewProvider.swift at lines 15‑16 and 67‑70.

How does Vorssaint use ScreenCaptureKit to capture window thumbnails?

Vorssaint utilizes SCShareableContent.excludingDesktopWindows to enumerate all windows across spaces, creates an SCContentFilter targeting a specific window, and calls SCScreenshotManager.captureImage with an SCStreamConfiguration to generate the final CGImage.

What is the fallback mechanism when Screen Recording permission is unavailable?

When permission is absent, the capture routine aborts before invoking ScreenCaptureKit, and the switcher interface falls back to displaying static application icons sourced from the app bundle rather than live window previews.

Where is the thumbnail capture logic implemented in the Vorssaint codebase?

The primary implementation resides in Sources/Vorssaint/Services/Switcher/WindowPreviewProvider.swift, with permission state management handled in Sources/Vorssaint/Core/Permissions.swift. The codebase also contains a private API fallback path using CGSHWCaptureWindowList for older macOS versions.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →