mirror of
https://github.com/samsonjs/Peekaboo.git
synced 2026-03-25 09:25:47 +00:00
Fix async concurrency issues without semaphores
- Replace problematic DispatchSemaphore usage with NSCondition-based async bridge - Revert to ParsableCommand for compatibility while maintaining async operations - Use CGWindowListCopyWindowInfo for sync permission checking instead of async ScreenCaptureKit - Remove all RunLoop workarounds in favor of proper Task.runBlocking pattern - Eliminate all deadlock sources while preserving async capture functionality
This commit is contained in:
parent
d2fb50b289
commit
50984f8dc2
3 changed files with 41 additions and 53 deletions
33
peekaboo-cli/Sources/peekaboo/AsyncUtils.swift
Normal file
33
peekaboo-cli/Sources/peekaboo/AsyncUtils.swift
Normal file
|
|
@ -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<T>(operation: @escaping () async throws -> T) throws -> T {
|
||||
var result: Result<T, Error>?
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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<Bool, Error>?
|
||||
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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue