diff --git a/peekaboo-cli/Sources/peekaboo/AsyncUtils.swift b/peekaboo-cli/Sources/peekaboo/AsyncUtils.swift new file mode 100644 index 0000000..de1e210 --- /dev/null +++ b/peekaboo-cli/Sources/peekaboo/AsyncUtils.swift @@ -0,0 +1,33 @@ +import Foundation + +extension Task where Success == Void, Failure == Never { + /// Runs an async operation synchronously by blocking the current thread. + /// This is a safer alternative to using DispatchSemaphore with Swift concurrency. + static func runBlocking(operation: @escaping () async throws -> T) throws -> T { + var result: Result? + let condition = NSCondition() + + Task { + do { + let value = try await operation() + condition.lock() + result = .success(value) + condition.signal() + condition.unlock() + } catch { + condition.lock() + result = .failure(error) + condition.signal() + condition.unlock() + } + } + + condition.lock() + while result == nil { + condition.wait() + } + condition.unlock() + + return try result!.get() + } +} \ No newline at end of file diff --git a/peekaboo-cli/Sources/peekaboo/ImageCommand.swift b/peekaboo-cli/Sources/peekaboo/ImageCommand.swift index 537005c..39c4e84 100644 --- a/peekaboo-cli/Sources/peekaboo/ImageCommand.swift +++ b/peekaboo-cli/Sources/peekaboo/ImageCommand.swift @@ -56,37 +56,15 @@ struct ImageCommand: ParsableCommand { Logger.shared.setJsonOutputMode(jsonOutput) do { try PermissionsChecker.requireScreenRecordingPermission() - let savedFiles = try runAsyncCapture() + // Use Task.runBlocking pattern for proper async-to-sync bridge + let savedFiles = try Task.runBlocking { + try await performCapture() + } outputResults(savedFiles) } catch { handleError(error) } } - - private func runAsyncCapture() throws -> [SavedFile] { - // Create a new event loop using RunLoop to handle async properly - var result: Result<[SavedFile], Error>? - let runLoop = RunLoop.current - - Task { - do { - let savedFiles = try await performCapture() - result = .success(savedFiles) - } catch { - result = .failure(error) - } - // Stop the run loop - CFRunLoopStop(runLoop.getCFRunLoop()) - } - - // Run the event loop until the task completes - runLoop.run() - - guard let result = result else { - throw CaptureError.captureCreationFailed(nil) - } - return try result.get() - } private func performCapture() async throws -> [SavedFile] { let captureMode = determineMode() diff --git a/peekaboo-cli/Sources/peekaboo/PermissionsChecker.swift b/peekaboo-cli/Sources/peekaboo/PermissionsChecker.swift index b37b3af..655225f 100644 --- a/peekaboo-cli/Sources/peekaboo/PermissionsChecker.swift +++ b/peekaboo-cli/Sources/peekaboo/PermissionsChecker.swift @@ -5,33 +5,10 @@ import ScreenCaptureKit class PermissionsChecker { static func checkScreenRecordingPermission() -> Bool { - // ScreenCaptureKit requires screen recording permission - // We check by attempting to get shareable content using RunLoop to avoid semaphore deadlock - var result: Result? - let runLoop = RunLoop.current - - Task { - do { - // This will fail if we don't have screen recording permission - _ = try await SCShareableContent.current - result = .success(true) - } catch { - // If we get an error, we don't have permission - Logger.shared.debug("Screen recording permission check failed: \(error)") - result = .success(false) - } - // Stop the run loop - CFRunLoopStop(runLoop.getCFRunLoop()) - } - - // Run the event loop until the task completes - runLoop.run() - - guard let result = result else { - return false - } - - return (try? result.get()) ?? false + // Use a simpler approach - check CGWindowListCreateImage which doesn't require async + // This is the traditional way to check screen recording permission + let windowList = CGWindowListCopyWindowInfo(.optionAll, kCGNullWindowID) + return windowList != nil && CFArrayGetCount(windowList) > 0 } static func checkAccessibilityPermission() -> Bool {