diff --git a/peekaboo-cli/Sources/peekaboo/AsyncAdapter.swift b/peekaboo-cli/Sources/peekaboo/AsyncAdapter.swift new file mode 100644 index 0000000..93dc1b8 --- /dev/null +++ b/peekaboo-cli/Sources/peekaboo/AsyncAdapter.swift @@ -0,0 +1,140 @@ +import Foundation +import ArgumentParser + +// MARK: - Adapter for AsyncParsableCommand to ParsableCommand bridge + +protocol AsyncRunnable { + func runAsync() async throws +} + +extension PeekabooCommand { + func run() throws { + let box = ErrorBox() + let sem = DispatchSemaphore(value: 0) + Task { + defer { sem.signal() } + do { + try await (self as AsyncRunnable).runAsync() + } catch { + box.error = error + } + } + sem.wait() + if let error = box.error { + throw error + } + } +} + +extension ImageCommand { + func run() throws { + let box = ErrorBox() + let sem = DispatchSemaphore(value: 0) + Task { + defer { sem.signal() } + do { + try await (self as AsyncRunnable).runAsync() + } catch { + box.error = error + } + } + sem.wait() + if let error = box.error { + throw error + } + } +} + +extension ListCommand { + func run() throws { + let box = ErrorBox() + let sem = DispatchSemaphore(value: 0) + Task { + defer { sem.signal() } + do { + try await (self as AsyncRunnable).runAsync() + } catch { + box.error = error + } + } + sem.wait() + if let error = box.error { + throw error + } + } +} + +extension AppsSubcommand { + func run() throws { + let box = ErrorBox() + let sem = DispatchSemaphore(value: 0) + Task { + defer { sem.signal() } + do { + try await (self as AsyncRunnable).runAsync() + } catch { + box.error = error + } + } + sem.wait() + if let error = box.error { + throw error + } + } +} + +extension WindowsSubcommand { + func run() throws { + let box = ErrorBox() + let sem = DispatchSemaphore(value: 0) + Task { + defer { sem.signal() } + do { + try await (self as AsyncRunnable).runAsync() + } catch { + box.error = error + } + } + sem.wait() + if let error = box.error { + throw error + } + } +} + +extension ServerStatusSubcommand { + func run() throws { + let box = ErrorBox() + let sem = DispatchSemaphore(value: 0) + Task { + defer { sem.signal() } + do { + try await (self as AsyncRunnable).runAsync() + } catch { + box.error = error + } + } + sem.wait() + if let error = box.error { + throw error + } + } +} + +private final class ErrorBox: @unchecked Sendable { + private var _error: Error? = nil + private let lock = NSLock() + + var error: Error? { + get { + lock.lock() + defer { lock.unlock() } + return _error + } + set { + lock.lock() + defer { lock.unlock() } + _error = newValue + } + } +} \ No newline at end of file diff --git a/peekaboo-cli/Sources/peekaboo/ImageCommand.swift b/peekaboo-cli/Sources/peekaboo/ImageCommand.swift index bc29e40..a48a7b0 100644 --- a/peekaboo-cli/Sources/peekaboo/ImageCommand.swift +++ b/peekaboo-cli/Sources/peekaboo/ImageCommand.swift @@ -19,7 +19,7 @@ struct FileHandleTextOutputStream: TextOutputStream { } } -struct ImageCommand: AsyncParsableCommand { +struct ImageCommand: ParsableCommand, AsyncRunnable { static let configuration = CommandConfiguration( commandName: "image", abstract: "Capture screen or window images" @@ -52,7 +52,7 @@ struct ImageCommand: AsyncParsableCommand { @Flag(name: .long, help: "Output results in JSON format") var jsonOutput = false - func run() async throws { + func runAsync() async throws { Logger.shared.setJsonOutputMode(jsonOutput) do { try PermissionsChecker.requireScreenRecordingPermission() diff --git a/peekaboo-cli/Sources/peekaboo/ListCommand.swift b/peekaboo-cli/Sources/peekaboo/ListCommand.swift index c0dee03..811ad31 100644 --- a/peekaboo-cli/Sources/peekaboo/ListCommand.swift +++ b/peekaboo-cli/Sources/peekaboo/ListCommand.swift @@ -2,7 +2,7 @@ import AppKit import ArgumentParser import Foundation -struct ListCommand: AsyncParsableCommand { +struct ListCommand: ParsableCommand, AsyncRunnable { static let configuration = CommandConfiguration( commandName: "list", abstract: "List running applications or windows", @@ -10,12 +10,12 @@ struct ListCommand: AsyncParsableCommand { defaultSubcommand: AppsSubcommand.self ) - func run() async throws { + func runAsync() async throws { // Root command doesn't do anything, subcommands handle everything } } -struct AppsSubcommand: AsyncParsableCommand { +struct AppsSubcommand: ParsableCommand, AsyncRunnable { static let configuration = CommandConfiguration( commandName: "apps", abstract: "List all running applications" @@ -24,7 +24,7 @@ struct AppsSubcommand: AsyncParsableCommand { @Flag(name: .long, help: "Output results in JSON format") var jsonOutput = false - func run() async throws { + func runAsync() async throws { Logger.shared.setJsonOutputMode(jsonOutput) do { @@ -102,7 +102,7 @@ struct AppsSubcommand: AsyncParsableCommand { } } -struct WindowsSubcommand: AsyncParsableCommand { +struct WindowsSubcommand: ParsableCommand, AsyncRunnable { static let configuration = CommandConfiguration( commandName: "windows", abstract: "List windows for a specific application" @@ -117,7 +117,7 @@ struct WindowsSubcommand: AsyncParsableCommand { @Flag(name: .long, help: "Output results in JSON format") var jsonOutput = false - func run() async throws { + func runAsync() async throws { Logger.shared.setJsonOutputMode(jsonOutput) do { @@ -249,7 +249,7 @@ struct WindowsSubcommand: AsyncParsableCommand { } } -struct ServerStatusSubcommand: AsyncParsableCommand { +struct ServerStatusSubcommand: ParsableCommand, AsyncRunnable { static let configuration = CommandConfiguration( commandName: "server_status", abstract: "Check server permissions status" @@ -258,7 +258,7 @@ struct ServerStatusSubcommand: AsyncParsableCommand { @Flag(name: .long, help: "Output results in JSON format") var jsonOutput = false - func run() async throws { + func runAsync() async throws { Logger.shared.setJsonOutputMode(jsonOutput) let screenRecording = PermissionsChecker.checkScreenRecordingPermission() diff --git a/peekaboo-cli/Sources/peekaboo/Version.swift b/peekaboo-cli/Sources/peekaboo/Version.swift index 0fbf43d..8cb81fb 100644 --- a/peekaboo-cli/Sources/peekaboo/Version.swift +++ b/peekaboo-cli/Sources/peekaboo/Version.swift @@ -1,4 +1,4 @@ // This file is auto-generated by the build script. Do not edit manually. -enum Version: Sendable { +enum Version { static let current = "1.0.0-beta.23" } diff --git a/peekaboo-cli/Sources/peekaboo/main.swift b/peekaboo-cli/Sources/peekaboo/main.swift index 46f07ea..d6c3ed5 100644 --- a/peekaboo-cli/Sources/peekaboo/main.swift +++ b/peekaboo-cli/Sources/peekaboo/main.swift @@ -2,7 +2,7 @@ import ArgumentParser import Foundation @available(macOS 10.15, *) -struct PeekabooCommand: AsyncParsableCommand { +struct PeekabooCommand: ParsableCommand, AsyncRunnable { static let configuration = CommandConfiguration( commandName: "peekaboo", abstract: "A macOS utility for screen capture, application listing, and window management", @@ -11,7 +11,7 @@ struct PeekabooCommand: AsyncParsableCommand { defaultSubcommand: ImageCommand.self ) - func run() async throws { + func runAsync() async throws { // Root command doesn't do anything, subcommands handle everything } }