Apply SwiftFormat changes for v1.0.0 release

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Peter Steinberger 2025-06-08 20:35:27 +01:00
parent 1a2a817822
commit 0bec93e364
14 changed files with 95 additions and 77 deletions

View file

@ -26,7 +26,7 @@ final class ApplicationFinder: Sendable {
// Find all possible matches // Find all possible matches
let allMatches = findAllMatches(for: identifier, in: runningApps) let allMatches = findAllMatches(for: identifier, in: runningApps)
// Filter out browser helpers for common browser searches // Filter out browser helpers for common browser searches
let matches = filterBrowserHelpers(matches: allMatches, identifier: identifier) let matches = filterBrowserHelpers(matches: allMatches, identifier: identifier)
@ -180,7 +180,7 @@ final class ApplicationFinder: Sendable {
// Provide browser-specific error messages // Provide browser-specific error messages
let browserIdentifiers = ["chrome", "safari", "firefox", "edge", "brave", "arc", "opera"] let browserIdentifiers = ["chrome", "safari", "firefox", "edge", "brave", "arc", "opera"]
let lowerIdentifier = identifier.lowercased() let lowerIdentifier = identifier.lowercased()
if browserIdentifiers.contains(lowerIdentifier) { if browserIdentifiers.contains(lowerIdentifier) {
// Logger.shared.error("\(identifier.capitalized) browser is not running or not found") // Logger.shared.error("\(identifier.capitalized) browser is not running or not found")
} else { } else {
@ -319,48 +319,49 @@ final class ApplicationFinder: Sendable {
return count return count
} }
private static func filterBrowserHelpers(matches: [AppMatch], identifier: String) -> [AppMatch] { private static func filterBrowserHelpers(matches: [AppMatch], identifier: String) -> [AppMatch] {
// Define common browser identifiers that should filter out helpers // Define common browser identifiers that should filter out helpers
let browserIdentifiers = ["chrome", "safari", "firefox", "edge", "brave", "arc", "opera"] let browserIdentifiers = ["chrome", "safari", "firefox", "edge", "brave", "arc", "opera"]
let lowerIdentifier = identifier.lowercased() let lowerIdentifier = identifier.lowercased()
// Check if the search is for a common browser // Check if the search is for a common browser
guard browserIdentifiers.contains(lowerIdentifier) else { guard browserIdentifiers.contains(lowerIdentifier) else {
return matches // No filtering for non-browser searches return matches // No filtering for non-browser searches
} }
// Logger.shared.debug("Filtering browser helpers for '\(identifier)' search") // Logger.shared.debug("Filtering browser helpers for '\(identifier)' search")
// Filter out helper processes for browser searches // Filter out helper processes for browser searches
let filteredMatches = matches.filter { match in let filteredMatches = matches.filter { match in
guard let appName = match.app.localizedName?.lowercased() else { return true } guard let appName = match.app.localizedName?.lowercased() else { return true }
// Exclude obvious helper processes // Exclude obvious helper processes
let isHelper = appName.contains("helper") || let isHelper = appName.contains("helper") ||
appName.contains("renderer") || appName.contains("renderer") ||
appName.contains("utility") || appName.contains("utility") ||
appName.contains("plugin") || appName.contains("plugin") ||
appName.contains("service") || appName.contains("service") ||
appName.contains("crashpad") || appName.contains("crashpad") ||
appName.contains("gpu") || appName.contains("gpu") ||
appName.contains("background") appName.contains("background")
if isHelper { if isHelper {
// Logger.shared.debug("Filtering out helper process: \(appName)") // Logger.shared.debug("Filtering out helper process: \(appName)")
return false return false
} }
return true return true
} }
// If we filtered out all matches, return the original matches to avoid "not found" errors // If we filtered out all matches, return the original matches to avoid "not found" errors
// But log a warning about this case // But log a warning about this case
if filteredMatches.isEmpty && !matches.isEmpty { if filteredMatches.isEmpty && !matches.isEmpty {
// Logger.shared.debug("All matches were filtered as helpers, returning original matches to avoid 'not found' error") // Logger.shared.debug("All matches were filtered as helpers, returning original matches to avoid 'not
// found' error")
return matches return matches
} }
// Logger.shared.debug("After browser helper filtering: \(filteredMatches.count) matches remaining") // Logger.shared.debug("After browser helper filtering: \(filteredMatches.count) matches remaining")
return filteredMatches return filteredMatches
} }

View file

@ -164,10 +164,15 @@ struct ImageCommand: AsyncParsableCommand {
return savedFiles return savedFiles
} }
private func captureAllScreensWithFallback(displays: [CGDirectDisplayID]) async throws(CaptureError) -> [SavedFile] { private func captureAllScreensWithFallback(displays: [CGDirectDisplayID]) async throws(CaptureError)
-> [SavedFile] {
var savedFiles: [SavedFile] = [] var savedFiles: [SavedFile] = []
for (index, displayID) in displays.enumerated() { for (index, displayID) in displays.enumerated() {
let savedFile = try await captureSingleDisplayWithFallback(displayID: displayID, index: index, labelSuffix: "") let savedFile = try await captureSingleDisplayWithFallback(
displayID: displayID,
index: index,
labelSuffix: ""
)
savedFiles.append(savedFile) savedFiles.append(savedFile)
} }
return savedFiles return savedFiles
@ -243,12 +248,12 @@ struct ImageCommand: AsyncParsableCommand {
let availableTitles = windows.map { "\"\($0.title)\"" }.joined(separator: ", ") let availableTitles = windows.map { "\"\($0.title)\"" }.joined(separator: ", ")
let searchTerm = windowTitle let searchTerm = windowTitle
let appName = targetApp.localizedName ?? "Unknown" let appName = targetApp.localizedName ?? "Unknown"
Logger.shared.debug( Logger.shared.debug(
"Window not found. Searched for '\(searchTerm)' in \(appName). " + "Window not found. Searched for '\(searchTerm)' in \(appName). " +
"Available windows: \(availableTitles)" "Available windows: \(availableTitles)"
) )
throw CaptureError.windowTitleNotFound(searchTerm, appName, availableTitles) throw CaptureError.windowTitleNotFound(searchTerm, appName, availableTitles)
} }
targetWindow = window targetWindow = window
@ -410,38 +415,38 @@ struct ImageCommand: AsyncParsableCommand {
throw CaptureError.windowCaptureFailed(error) throw CaptureError.windowCaptureFailed(error)
} }
} }
private func captureFrontmostWindow() async throws -> [SavedFile] { private func captureFrontmostWindow() async throws -> [SavedFile] {
Logger.shared.debug("Capturing frontmost window") Logger.shared.debug("Capturing frontmost window")
// Get the frontmost (active) application // Get the frontmost (active) application
guard let frontmostApp = NSWorkspace.shared.frontmostApplication else { guard let frontmostApp = NSWorkspace.shared.frontmostApplication else {
throw CaptureError.appNotFound("No frontmost application found") throw CaptureError.appNotFound("No frontmost application found")
} }
Logger.shared.debug("Frontmost app: \(frontmostApp.localizedName ?? "Unknown")") Logger.shared.debug("Frontmost app: \(frontmostApp.localizedName ?? "Unknown")")
// Get windows for the frontmost app // Get windows for the frontmost app
let windows = try WindowManager.getWindowsForApp(pid: frontmostApp.processIdentifier) let windows = try WindowManager.getWindowsForApp(pid: frontmostApp.processIdentifier)
guard !windows.isEmpty else { guard !windows.isEmpty else {
throw CaptureError.noWindowsFound(frontmostApp.localizedName ?? "frontmost application") throw CaptureError.noWindowsFound(frontmostApp.localizedName ?? "frontmost application")
} }
// Get the frontmost window (index 0) // Get the frontmost window (index 0)
let frontmostWindow = windows[0] let frontmostWindow = windows[0]
Logger.shared.debug("Capturing frontmost window: '\(frontmostWindow.title)'") Logger.shared.debug("Capturing frontmost window: '\(frontmostWindow.title)'")
// Generate output path // Generate output path
let timestamp = DateFormatter.timestamp.string(from: Date()) let timestamp = DateFormatter.timestamp.string(from: Date())
let appName = frontmostApp.localizedName ?? "UnknownApp" let appName = frontmostApp.localizedName ?? "UnknownApp"
let safeName = appName.replacingOccurrences(of: " ", with: "_") let safeName = appName.replacingOccurrences(of: " ", with: "_")
let fileName = "frontmost_\(safeName)_\(timestamp).\(format.rawValue)" let fileName = "frontmost_\(safeName)_\(timestamp).\(format.rawValue)"
let filePath = OutputPathResolver.getOutputPathWithFallback(basePath: path, fileName: fileName) let filePath = OutputPathResolver.getOutputPathWithFallback(basePath: path, fileName: fileName)
// Capture the window // Capture the window
try await captureWindow(frontmostWindow, to: filePath) try await captureWindow(frontmostWindow, to: filePath)
return [SavedFile( return [SavedFile(
path: filePath, path: filePath,
item_label: appName, item_label: appName,

View file

@ -1,7 +1,7 @@
import Foundation
import AppKit import AppKit
import Foundation
struct ImageErrorHandler { enum ImageErrorHandler {
static func handleError(_ error: Error, jsonOutput: Bool) { static func handleError(_ error: Error, jsonOutput: Bool) {
let captureError: CaptureError = if let err = error as? CaptureError { let captureError: CaptureError = if let err = error as? CaptureError {
err err

View file

@ -1,5 +1,5 @@
import Foundation
import CoreGraphics import CoreGraphics
import Foundation
import ImageIO import ImageIO
import UniformTypeIdentifiers import UniformTypeIdentifiers

View file

@ -7,11 +7,17 @@ struct JSONResponse: Codable {
let debug_logs: [String] let debug_logs: [String]
let error: ErrorInfo? let error: ErrorInfo?
init(success: Bool, data: Any? = nil, messages: [String]? = nil, debugLogs: [String] = [], error: ErrorInfo? = nil) { init(
success: Bool,
data: Any? = nil,
messages: [String]? = nil,
debugLogs: [String] = [],
error: ErrorInfo? = nil
) {
self.success = success self.success = success
self.data = data.map(AnyCodable.init) self.data = data.map(AnyCodable.init)
self.messages = messages self.messages = messages
self.debug_logs = debugLogs debug_logs = debugLogs
self.error = error self.error = error
} }
} }

View file

@ -9,7 +9,7 @@ struct ListCommand: AsyncParsableCommand {
subcommands: [AppsSubcommand.self, WindowsSubcommand.self, ServerStatusSubcommand.self], subcommands: [AppsSubcommand.self, WindowsSubcommand.self, ServerStatusSubcommand.self],
defaultSubcommand: AppsSubcommand.self defaultSubcommand: AppsSubcommand.self
) )
func run() async throws { func run() async throws {
// Root command doesn't do anything, subcommands handle everything // Root command doesn't do anything, subcommands handle everything
} }

View file

@ -61,10 +61,10 @@ struct WindowBounds: Codable, Sendable {
let y_coordinate: Int let y_coordinate: Int
let width: Int let width: Int
let height: Int let height: Int
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case x_coordinate = "x_coordinate" case x_coordinate
case y_coordinate = "y_coordinate" case y_coordinate
case width case width
case height case height
} }
@ -149,7 +149,8 @@ enum CaptureError: Error, LocalizedError, Sendable {
if !availableTitles.isEmpty { if !availableTitles.isEmpty {
message += " Available windows: \(availableTitles)." message += " Available windows: \(availableTitles)."
} }
message += " Note: For URLs, try without the protocol (e.g., 'example.com:8080' instead of 'http://example.com:8080')." message +=
" Note: For URLs, try without the protocol (e.g., 'example.com:8080' instead of 'http://example.com:8080')."
return message return message
case let .windowCaptureFailed(underlyingError): case let .windowCaptureFailed(underlyingError):
var message = "Failed to capture the specified window." var message = "Failed to capture the specified window."

View file

@ -2,7 +2,7 @@ import Foundation
struct OutputPathResolver: Sendable { struct OutputPathResolver: Sendable {
static func getOutputPath(basePath: String?, fileName: String, screenIndex: Int? = nil) -> String { static func getOutputPath(basePath: String?, fileName: String, screenIndex: Int? = nil) -> String {
if let basePath = basePath { if let basePath {
validatePath(basePath) validatePath(basePath)
return determineOutputPath(basePath: basePath, fileName: fileName, screenIndex: screenIndex) return determineOutputPath(basePath: basePath, fileName: fileName, screenIndex: screenIndex)
} else { } else {
@ -11,7 +11,7 @@ struct OutputPathResolver: Sendable {
} }
static func getOutputPathWithFallback(basePath: String?, fileName: String) -> String { static func getOutputPathWithFallback(basePath: String?, fileName: String) -> String {
if let basePath = basePath { if let basePath {
validatePath(basePath) validatePath(basePath)
return determineOutputPathWithFallback(basePath: basePath, fileName: fileName) return determineOutputPathWithFallback(basePath: basePath, fileName: fileName)
} else { } else {
@ -118,17 +118,17 @@ struct OutputPathResolver: Sendable {
return "\(basePath)/\(fileName)" return "\(basePath)/\(fileName)"
} }
} }
private static func validatePath(_ path: String) { private static func validatePath(_ path: String) {
// Check for path traversal attempts // Check for path traversal attempts
if path.contains("../") || path.contains("..\\") { if path.contains("../") || path.contains("..\\") {
// Logger.shared.debug("Potential path traversal detected in path: \(path)") // Logger.shared.debug("Potential path traversal detected in path: \(path)")
} }
// Check for system-sensitive paths // Check for system-sensitive paths
let sensitivePathPrefixes = ["/etc/", "/usr/", "/bin/", "/sbin/", "/System/", "/Library/System/"] let sensitivePathPrefixes = ["/etc/", "/usr/", "/bin/", "/sbin/", "/System/", "/Library/System/"]
let normalizedPath = (path as NSString).standardizingPath let normalizedPath = (path as NSString).standardizingPath
for prefix in sensitivePathPrefixes where normalizedPath.hasPrefix(prefix) { for prefix in sensitivePathPrefixes where normalizedPath.hasPrefix(prefix) {
// Logger.shared.debug("Path points to system directory: \(path) -> \(normalizedPath)") // Logger.shared.debug("Path points to system directory: \(path) -> \(normalizedPath)")
break break

View file

@ -1,5 +1,5 @@
import Foundation
import CoreGraphics import CoreGraphics
import Foundation
@preconcurrency import ScreenCaptureKit @preconcurrency import ScreenCaptureKit
struct ScreenCapture: Sendable { struct ScreenCapture: Sendable {

View file

@ -11,8 +11,8 @@ struct PeekabooCommand: AsyncParsableCommand {
subcommands: [ImageCommand.self, ListCommand.self], subcommands: [ImageCommand.self, ListCommand.self],
defaultSubcommand: ImageCommand.self defaultSubcommand: ImageCommand.self
) )
func run() async throws { func run() async throws {
// Root command doesn't do anything, subcommands handle everything // Root command doesn't do anything, subcommands handle everything
} }
} }

View file

@ -423,14 +423,14 @@ struct ApplicationFinderEdgeCaseTests {
} }
} }
} }
// MARK: - Browser Helper Filtering Tests // MARK: - Browser Helper Filtering Tests
@Test("Browser helper filtering for Chrome searches", .tags(.browserFiltering)) @Test("Browser helper filtering for Chrome searches", .tags(.browserFiltering))
func browserHelperFilteringChrome() { func browserHelperFilteringChrome() {
// Test that Chrome helper processes are filtered out when searching for "chrome" // Test that Chrome helper processes are filtered out when searching for "chrome"
// Note: This test documents expected behavior even when Chrome isn't running // Note: This test documents expected behavior even when Chrome isn't running
do { do {
let result = try ApplicationFinder.findApplication(identifier: "chrome") let result = try ApplicationFinder.findApplication(identifier: "chrome")
// If found, should be the main Chrome app, not a helper // If found, should be the main Chrome app, not a helper
@ -446,11 +446,11 @@ struct ApplicationFinderEdgeCaseTests {
print("Chrome not found, which is acceptable for browser helper filtering test") print("Chrome not found, which is acceptable for browser helper filtering test")
} }
} }
@Test("Browser helper filtering for Safari searches", .tags(.browserFiltering)) @Test("Browser helper filtering for Safari searches", .tags(.browserFiltering))
func browserHelperFilteringSafari() { func browserHelperFilteringSafari() {
// Test that Safari helper processes are filtered out when searching for "safari" // Test that Safari helper processes are filtered out when searching for "safari"
do { do {
let result = try ApplicationFinder.findApplication(identifier: "safari") let result = try ApplicationFinder.findApplication(identifier: "safari")
// If found, should be the main Safari app, not a helper // If found, should be the main Safari app, not a helper
@ -465,14 +465,14 @@ struct ApplicationFinderEdgeCaseTests {
print("Safari not found, which is acceptable for browser helper filtering test") print("Safari not found, which is acceptable for browser helper filtering test")
} }
} }
@Test("Non-browser searches should not filter helpers", .tags(.browserFiltering)) @Test("Non-browser searches should not filter helpers", .tags(.browserFiltering))
func nonBrowserSearchesPreserveHelpers() { func nonBrowserSearchesPreserveHelpers() {
// Test that non-browser searches still find helper processes if that's what's being searched for // Test that non-browser searches still find helper processes if that's what's being searched for
// This tests that helper filtering only applies to browser identifiers // This tests that helper filtering only applies to browser identifiers
let nonBrowserIdentifiers = ["finder", "textedit", "calculator", "activity monitor"] let nonBrowserIdentifiers = ["finder", "textedit", "calculator", "activity monitor"]
for identifier in nonBrowserIdentifiers { for identifier in nonBrowserIdentifiers {
do { do {
let result = try ApplicationFinder.findApplication(identifier: identifier) let result = try ApplicationFinder.findApplication(identifier: identifier)
@ -484,13 +484,13 @@ struct ApplicationFinderEdgeCaseTests {
} }
} }
} }
@Test("Browser error messages are more specific", .tags(.browserFiltering)) @Test("Browser error messages are more specific", .tags(.browserFiltering))
func browserSpecificErrorMessages() { func browserSpecificErrorMessages() {
// Test that browser-specific error messages are provided when browsers aren't found // Test that browser-specific error messages are provided when browsers aren't found
let browserIdentifiers = ["chrome", "firefox", "edge"] let browserIdentifiers = ["chrome", "firefox", "edge"]
for browser in browserIdentifiers { for browser in browserIdentifiers {
do { do {
_ = try ApplicationFinder.findApplication(identifier: browser) _ = try ApplicationFinder.findApplication(identifier: browser)

View file

@ -263,7 +263,6 @@ struct ImageCommandTests {
#expect(command.screenIndex == index) #expect(command.screenIndex == index)
} }
@Test( @Test(
"Window index boundary values", "Window index boundary values",
arguments: [0, 1, 10, 9999] arguments: [0, 1, 10, 9999]
@ -273,7 +272,6 @@ struct ImageCommandTests {
#expect(command.windowIndex == index) #expect(command.windowIndex == index)
} }
@Test("Error handling for invalid combinations", .tags(.fast)) @Test("Error handling for invalid combinations", .tags(.fast))
func invalidCombinations() { func invalidCombinations() {
// Window capture without app should fail in execution // Window capture without app should fail in execution
@ -340,7 +338,11 @@ struct ImageCommandPathHandlingTests {
func singleScreenFilePath() { func singleScreenFilePath() {
// For single screen, should use exact path // For single screen, should use exact path
let fileName = "screen_1_20250608_120000.png" let fileName = "screen_1_20250608_120000.png"
let result = OutputPathResolver.determineOutputPath(basePath: "/tmp/my-screenshot.png", fileName: fileName, screenIndex: 0) let result = OutputPathResolver.determineOutputPath(
basePath: "/tmp/my-screenshot.png",
fileName: fileName,
screenIndex: 0
)
#expect(result == "/tmp/my-screenshot.png") #expect(result == "/tmp/my-screenshot.png")
} }
@ -575,7 +577,10 @@ struct ImageCommandErrorHandlingTests {
// This test validates the logic without actually creating directories // This test validates the logic without actually creating directories
let fileName = "screen_1_20250608_120001.png" let fileName = "screen_1_20250608_120001.png"
let result = OutputPathResolver.determineOutputPath(basePath: "/tmp/test-path-creation/file.png", fileName: fileName) let result = OutputPathResolver.determineOutputPath(
basePath: "/tmp/test-path-creation/file.png",
fileName: fileName
)
// Should return the intended path even if directory creation might fail // Should return the intended path even if directory creation might fail
#expect(result == "/tmp/test-path-creation/file_1_20250608_120001.png") #expect(result == "/tmp/test-path-creation/file_1_20250608_120001.png")

View file

@ -12,7 +12,7 @@ struct JSONOutputTests {
struct TestWrapper: Codable { struct TestWrapper: Codable {
let value: AnyCodable let value: AnyCodable
} }
// Test string // Test string
let stringWrapper = TestWrapper(value: AnyCodable("test string")) let stringWrapper = TestWrapper(value: AnyCodable("test string"))
let stringData = try JSONEncoder().encode(stringWrapper) let stringData = try JSONEncoder().encode(stringWrapper)

View file

@ -139,21 +139,21 @@ struct ScreenshotValidationTests {
// 1. The physical pixel dimensions of the display // 1. The physical pixel dimensions of the display
// 2. How macOS reports display information // 2. How macOS reports display information
// 3. Whether the display is Retina or not // 3. Whether the display is Retina or not
// //
// Instead of trying to match exact dimensions, verify: // Instead of trying to match exact dimensions, verify:
// - The image has reasonable dimensions // - The image has reasonable dimensions
// - The aspect ratio is preserved // - The aspect ratio is preserved
#expect(image.size.width > 0) #expect(image.size.width > 0)
#expect(image.size.height > 0) #expect(image.size.height > 0)
#expect(image.size.width <= 8192) // Max reasonable display width #expect(image.size.width <= 8192) // Max reasonable display width
#expect(image.size.height <= 8192) // Max reasonable display height #expect(image.size.height <= 8192) // Max reasonable display height
// Verify aspect ratio is reasonable (between 1:3 and 3:1) // Verify aspect ratio is reasonable (between 1:3 and 3:1)
let aspectRatio = image.size.width / image.size.height let aspectRatio = image.size.width / image.size.height
#expect(aspectRatio > 0.33) #expect(aspectRatio > 0.33)
#expect(aspectRatio < 3.0) #expect(aspectRatio < 3.0)
print("Display \(index): captured \(image.size.width)x\(image.size.height)") print("Display \(index): captured \(image.size.width)x\(image.size.height)")
} }
} catch { } catch {
@ -204,7 +204,7 @@ struct ScreenshotValidationTests {
// - Whether screen recording permission dialogs appear // - Whether screen recording permission dialogs appear
#expect(averageTime < 1.5) // Average should be under 1.5 seconds #expect(averageTime < 1.5) // Average should be under 1.5 seconds
#expect(maxTime < 3.0) // Max should be under 3 seconds #expect(maxTime < 3.0) // Max should be under 3 seconds
// Performance benchmarks on typical hardware: // Performance benchmarks on typical hardware:
// - Single 1080p display: ~100-200ms // - Single 1080p display: ~100-200ms
// - Single 4K display: ~300-500ms // - Single 4K display: ~300-500ms
@ -315,18 +315,18 @@ struct ScreenshotValidationTests {
format: ImageFormat format: ImageFormat
) async throws -> ImageCaptureData { ) async throws -> ImageCaptureData {
let availableContent = try await SCShareableContent.current let availableContent = try await SCShareableContent.current
guard let scDisplay = availableContent.displays.first(where: { $0.displayID == displayID }) else { guard let scDisplay = availableContent.displays.first(where: { $0.displayID == displayID }) else {
throw CaptureError.captureCreationFailed(nil) throw CaptureError.captureCreationFailed(nil)
} }
let filter = SCContentFilter(display: scDisplay, excludingWindows: []) let filter = SCContentFilter(display: scDisplay, excludingWindows: [])
let configuration = SCStreamConfiguration() let configuration = SCStreamConfiguration()
configuration.backgroundColor = .clear configuration.backgroundColor = .clear
configuration.shouldBeOpaque = true configuration.shouldBeOpaque = true
configuration.showsCursor = false configuration.showsCursor = false
let image = try await SCScreenshotManager.captureImage( let image = try await SCScreenshotManager.captureImage(
contentFilter: filter, contentFilter: filter,
configuration: configuration configuration: configuration