From f6e9cbc7b9b35fa572f27469afda87d279551edb Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 27 May 2025 01:22:52 +0200 Subject: [PATCH] Migrate to ScreenCaptureKit --- .../Sources/peekaboo/ImageCommand.swift | 109 ++++++++++++++++-- 1 file changed, 97 insertions(+), 12 deletions(-) diff --git a/peekaboo-cli/Sources/peekaboo/ImageCommand.swift b/peekaboo-cli/Sources/peekaboo/ImageCommand.swift index 08be178..3d16f66 100644 --- a/peekaboo-cli/Sources/peekaboo/ImageCommand.swift +++ b/peekaboo-cli/Sources/peekaboo/ImageCommand.swift @@ -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) }