Peekaboo/peekaboo-cli/Sources/peekaboo/Models.swift
Peter Steinberger ed1860d546 feat: Improve error propagation and debugging for system-level failures
- Enhanced CaptureError types to include underlying system errors
- Added comprehensive error logging in debug_logs for troubleshooting
- Fixed duplicate error output from ApplicationFinder
- Improved error details for app not found to show available applications
- Updated test expectations to match new error message formats

This ensures that errors from deep within ScreenCaptureKit and file operations
are properly surfaced to users with full context in the debug logs.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-08 07:08:35 +01:00

194 lines
5.7 KiB
Swift

import ArgumentParser
import Foundation
// MARK: - Image Capture Models
struct SavedFile: Codable {
let path: String
let item_label: String?
let window_title: String?
let window_id: UInt32?
let window_index: Int?
let mime_type: String
}
struct ImageCaptureData: Codable {
let saved_files: [SavedFile]
}
enum CaptureMode: String, CaseIterable, ExpressibleByArgument {
case screen
case window
case multi
}
enum ImageFormat: String, CaseIterable, ExpressibleByArgument {
case png
case jpg
}
enum CaptureFocus: String, CaseIterable, ExpressibleByArgument {
case background
case auto
case foreground
}
// MARK: - Application & Window Models
struct ApplicationInfo: Codable {
let app_name: String
let bundle_id: String
let pid: Int32
let is_active: Bool
let window_count: Int
}
struct ApplicationListData: Codable {
let applications: [ApplicationInfo]
}
struct WindowInfo: Codable {
let window_title: String
let window_id: UInt32?
let window_index: Int?
let bounds: WindowBounds?
let is_on_screen: Bool?
}
struct WindowBounds: Codable {
let xCoordinate: Int
let yCoordinate: Int
let width: Int
let height: Int
}
struct TargetApplicationInfo: Codable {
let app_name: String
let bundle_id: String?
let pid: Int32
}
struct WindowListData: Codable {
let windows: [WindowInfo]
let target_application_info: TargetApplicationInfo
}
// MARK: - Window Specifier
enum WindowSpecifier {
case title(String)
case index(Int)
}
// MARK: - Window Details Options
enum WindowDetailOption: String, CaseIterable {
case off_screen
case bounds
case ids
}
// MARK: - Window Management
struct WindowData {
let windowId: UInt32
let title: String
let bounds: CGRect
let isOnScreen: Bool
let windowIndex: Int
}
// MARK: - Error Types
enum CaptureError: Error, LocalizedError {
case noDisplaysAvailable
case screenRecordingPermissionDenied
case accessibilityPermissionDenied
case invalidDisplayID
case captureCreationFailed(Error?)
case windowNotFound
case windowCaptureFailed(Error?)
case fileWriteError(String, Error?)
case appNotFound(String)
case invalidWindowIndex(Int)
case invalidArgument(String)
case unknownError(String)
case noWindowsFound(String)
var errorDescription: String? {
switch self {
case .noDisplaysAvailable:
return "No displays available for capture."
case .screenRecordingPermissionDenied:
return "Screen recording permission is required. " +
"Please grant it in System Settings > Privacy & Security > Screen Recording."
case .accessibilityPermissionDenied:
return "Accessibility permission is required for some operations. " +
"Please grant it in System Settings > Privacy & Security > Accessibility."
case .invalidDisplayID:
return "Invalid display ID provided."
case let .captureCreationFailed(underlyingError):
var message = "Failed to create the screen capture."
if let error = underlyingError {
message += " \(error.localizedDescription)"
}
return message
case .windowNotFound:
return "The specified window could not be found."
case let .windowCaptureFailed(underlyingError):
var message = "Failed to capture the specified window."
if let error = underlyingError {
message += " \(error.localizedDescription)"
}
return message
case let .fileWriteError(path, underlyingError):
var message = "Failed to write capture file to path: \(path)."
if let error = underlyingError {
let errorString = error.localizedDescription
if errorString.lowercased().contains("permission") {
message += " Permission denied - check that the directory is " +
"writable and the application has necessary permissions."
} else if errorString.lowercased().contains("no such file") {
message += " Directory does not exist - ensure the parent directory exists."
} else if errorString.lowercased().contains("no space") {
message += " Insufficient disk space available."
} else {
message += " \(errorString)"
}
} else {
message += " This may be due to insufficient permissions, missing directory, or disk space issues."
}
return message
case let .appNotFound(identifier):
return "Application with identifier '\(identifier)' not found or is not running."
case let .invalidWindowIndex(index):
return "Invalid window index: \(index)."
case let .invalidArgument(message):
return "Invalid argument: \(message)"
case let .unknownError(message):
return "An unexpected error occurred: \(message)"
case let .noWindowsFound(appName):
return "The '\(appName)' process is running, but no capturable windows were found."
}
}
var exitCode: Int32 {
switch self {
case .noDisplaysAvailable: 10
case .screenRecordingPermissionDenied: 11
case .accessibilityPermissionDenied: 12
case .invalidDisplayID: 13
case .captureCreationFailed: 14
case .windowNotFound: 15
case .windowCaptureFailed: 16
case .fileWriteError: 17
case .appNotFound: 18
case .invalidWindowIndex: 19
case .invalidArgument: 20
case .unknownError: 1
case .noWindowsFound: 7
}
}
}