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