mirror of
https://github.com/samsonjs/Peekaboo.git
synced 2026-04-26 14:57:47 +00:00
- Update to swift-tools-version 6.0 and enable StrictConcurrency - Make all data models and types Sendable for concurrency safety - Migrate commands from ParsableCommand to AsyncParsableCommand - Remove AsyncUtils.swift and synchronous bridging patterns - Update WindowBounds property names to snake_case for consistency - Ensure all error types conform to Sendable protocol - Add comprehensive Swift 6 migration documentation This migration enables full Swift 6 concurrency checking and data race safety while maintaining backward compatibility with the existing API. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
87 lines
3.4 KiB
Swift
87 lines
3.4 KiB
Swift
import Foundation
|
|
import CoreGraphics
|
|
import ScreenCaptureKit
|
|
|
|
struct ScreenCapture: Sendable {
|
|
static func captureDisplay(
|
|
_ displayID: CGDirectDisplayID, to path: String, format: ImageFormat = .png
|
|
) async throws {
|
|
do {
|
|
// Get available content
|
|
let availableContent = try await SCShareableContent.current
|
|
|
|
// Find the display by ID
|
|
guard let scDisplay = availableContent.displays.first(where: { $0.displayID == displayID }) else {
|
|
throw CaptureError.captureCreationFailed(nil)
|
|
}
|
|
|
|
// Create content filter for the entire display
|
|
let filter = SCContentFilter(display: scDisplay, excludingWindows: [])
|
|
|
|
// Configure capture settings
|
|
let configuration = SCStreamConfiguration()
|
|
configuration.width = scDisplay.width
|
|
configuration.height = scDisplay.height
|
|
configuration.backgroundColor = .black
|
|
configuration.shouldBeOpaque = true
|
|
configuration.showsCursor = true
|
|
|
|
// Capture the image
|
|
let image = try await SCScreenshotManager.captureImage(
|
|
contentFilter: filter,
|
|
configuration: configuration
|
|
)
|
|
|
|
try ImageSaver.saveImage(image, to: path, format: format)
|
|
} catch let captureError as CaptureError {
|
|
// Re-throw CaptureError as-is (no need to check for screen recording permission)
|
|
throw captureError
|
|
} catch {
|
|
// Check if this is a permission error from ScreenCaptureKit
|
|
if PermissionErrorDetector.isScreenRecordingPermissionError(error) {
|
|
throw CaptureError.screenRecordingPermissionDenied
|
|
}
|
|
throw error
|
|
}
|
|
}
|
|
|
|
static func captureWindow(_ window: WindowData, to path: String, format: ImageFormat = .png) async throws {
|
|
do {
|
|
// Get available content
|
|
let availableContent = try await SCShareableContent.current
|
|
|
|
// Find the window by ID
|
|
guard let scWindow = availableContent.windows.first(where: { $0.windowID == window.windowId }) else {
|
|
throw CaptureError.windowNotFound
|
|
}
|
|
|
|
// Create content filter for the specific window
|
|
let filter = SCContentFilter(desktopIndependentWindow: scWindow)
|
|
|
|
// Configure capture settings
|
|
let configuration = SCStreamConfiguration()
|
|
configuration.width = Int(window.bounds.width)
|
|
configuration.height = Int(window.bounds.height)
|
|
configuration.backgroundColor = .clear
|
|
configuration.shouldBeOpaque = true
|
|
configuration.showsCursor = false
|
|
|
|
// Capture the image
|
|
let image = try await SCScreenshotManager.captureImage(
|
|
contentFilter: filter,
|
|
configuration: configuration
|
|
)
|
|
|
|
try ImageSaver.saveImage(image, to: path, format: format)
|
|
} catch let captureError as CaptureError {
|
|
// Re-throw CaptureError as-is (no need to check for screen recording permission)
|
|
throw captureError
|
|
} catch {
|
|
// Check if this is a permission error from ScreenCaptureKit
|
|
if PermissionErrorDetector.isScreenRecordingPermissionError(error) {
|
|
throw CaptureError.screenRecordingPermissionDenied
|
|
}
|
|
throw error
|
|
}
|
|
}
|
|
}
|