Migrate to ScreenCaptureKit

This commit is contained in:
Peter Steinberger 2025-05-27 01:22:52 +02:00
parent 4636aec9d4
commit f6e9cbc7b9

View file

@ -2,6 +2,7 @@ import AppKit
import ArgumentParser
import CoreGraphics
import Foundation
import ScreenCaptureKit
import UniformTypeIdentifiers
// Define the wrapper struct
@ -199,7 +200,7 @@ struct ImageCommand: ParsableCommand {
if captureFocus == .foreground {
try PermissionsChecker.requireAccessibilityPermission()
targetApp.activate(options: [.activateIgnoringOtherApps])
targetApp.activate()
Thread.sleep(forTimeInterval: 0.2) // Brief delay for activation
}
@ -245,7 +246,7 @@ struct ImageCommand: ParsableCommand {
if captureFocus == .foreground {
try PermissionsChecker.requireAccessibilityPermission()
targetApp.activate(options: [.activateIgnoringOtherApps])
targetApp.activate()
Thread.sleep(forTimeInterval: 0.2)
}
@ -279,24 +280,108 @@ struct ImageCommand: ParsableCommand {
}
private func captureDisplay(_ displayID: CGDirectDisplayID, to path: String) throws(CaptureError) {
guard let image = CGDisplayCreateImage(displayID) else {
do {
let semaphore = DispatchSemaphore(value: 0)
var captureError: Error?
Task {
do {
try await captureDisplayWithScreenCaptureKit(displayID, to: path)
} catch {
captureError = error
}
semaphore.signal()
}
semaphore.wait()
if let error = captureError {
throw error
}
} catch {
throw CaptureError.captureCreationFailed
}
}
private func captureDisplayWithScreenCaptureKit(_ displayID: CGDirectDisplayID, to path: String) async throws {
// 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
}
// 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 saveImage(image, to: path)
}
private func captureWindow(_ window: WindowData, to path: String) throws(CaptureError) {
let options: CGWindowImageOption = [.boundsIgnoreFraming, .shouldBeOpaque]
guard let image = CGWindowListCreateImage(
window.bounds,
.optionIncludingWindow,
window.windowId,
options
) else {
do {
let semaphore = DispatchSemaphore(value: 0)
var captureError: Error?
Task {
do {
try await captureWindowWithScreenCaptureKit(window, to: path)
} catch {
captureError = error
}
semaphore.signal()
}
semaphore.wait()
if let error = captureError {
throw error
}
} catch {
throw CaptureError.windowCaptureFailed
}
}
private func captureWindowWithScreenCaptureKit(_ window: WindowData, to path: String) async throws {
// 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 saveImage(image, to: path)
}