mirror of
https://github.com/samsonjs/Peekaboo.git
synced 2026-03-25 09:25:47 +00:00
Adds support for capturing the frontmost window of the frontmost application instead of falling back to screen capture mode. Changes: - Added 'frontmost' case to CaptureMode enum in Swift CLI - Implemented captureFrontmostWindow() method using NSWorkspace.shared.frontmostApplication - Updated TypeScript to use --mode frontmost instead of defaulting to screen mode - Added comprehensive test coverage for frontmost functionality - Updated existing tests to reflect new behavior The frontmost mode now: 1. Detects the currently active application 2. Captures only its frontmost window (index 0) 3. Returns a single image file with proper metadata 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
137 lines
6 KiB
Swift
137 lines
6 KiB
Swift
import Foundation
|
|
|
|
struct OutputPathResolver {
|
|
static func getOutputPath(basePath: String?, fileName: String, screenIndex: Int? = nil) -> String {
|
|
if let basePath = basePath {
|
|
validatePath(basePath)
|
|
return determineOutputPath(basePath: basePath, fileName: fileName, screenIndex: screenIndex)
|
|
} else {
|
|
return "/tmp/\(fileName)"
|
|
}
|
|
}
|
|
|
|
static func getOutputPathWithFallback(basePath: String?, fileName: String) -> String {
|
|
if let basePath = basePath {
|
|
validatePath(basePath)
|
|
return determineOutputPathWithFallback(basePath: basePath, fileName: fileName)
|
|
} else {
|
|
return "/tmp/\(fileName)"
|
|
}
|
|
}
|
|
|
|
static func determineOutputPath(basePath: String, fileName: String, screenIndex: Int? = nil) -> String {
|
|
// Check if basePath looks like a file (has extension and doesn't end with /)
|
|
// Exclude special directory cases like "." and ".."
|
|
let isLikelyFile = basePath.contains(".") && !basePath.hasSuffix("/") &&
|
|
basePath != "." && basePath != ".."
|
|
|
|
if isLikelyFile {
|
|
// Create parent directory if needed
|
|
let parentDir = (basePath as NSString).deletingLastPathComponent
|
|
if !parentDir.isEmpty && parentDir != "/" {
|
|
do {
|
|
try FileManager.default.createDirectory(
|
|
atPath: parentDir,
|
|
withIntermediateDirectories: true,
|
|
attributes: nil
|
|
)
|
|
} catch {
|
|
// Log but don't fail - maybe directory already exists
|
|
// Logger.debug("Could not create parent directory \(parentDir): \(error)")
|
|
}
|
|
}
|
|
|
|
// For multiple screens, append screen index to avoid overwriting
|
|
if screenIndex == nil {
|
|
// Multiple screens - modify filename to include screen info
|
|
let pathExtension = (basePath as NSString).pathExtension
|
|
let pathWithoutExtension = (basePath as NSString).deletingPathExtension
|
|
|
|
// Extract screen info from fileName (e.g., "screen_1_20250608_120000.png" -> "1_20250608_120000")
|
|
let fileNameWithoutExt = (fileName as NSString).deletingPathExtension
|
|
let screenSuffix = fileNameWithoutExt.replacingOccurrences(of: "screen_", with: "")
|
|
|
|
return "\(pathWithoutExtension)_\(screenSuffix).\(pathExtension)"
|
|
}
|
|
|
|
return basePath
|
|
} else {
|
|
// Treat as directory - ensure it exists
|
|
do {
|
|
try FileManager.default.createDirectory(
|
|
atPath: basePath,
|
|
withIntermediateDirectories: true,
|
|
attributes: nil
|
|
)
|
|
} catch {
|
|
// Log but don't fail - maybe directory already exists
|
|
// Logger.debug("Could not create directory \(basePath): \(error)")
|
|
}
|
|
return "\(basePath)/\(fileName)"
|
|
}
|
|
}
|
|
|
|
static func determineOutputPathWithFallback(basePath: String, fileName: String) -> String {
|
|
// Check if basePath looks like a file (has extension and doesn't end with /)
|
|
// Exclude special directory cases like "." and ".."
|
|
let isLikelyFile = basePath.contains(".") && !basePath.hasSuffix("/") &&
|
|
basePath != "." && basePath != ".."
|
|
|
|
if isLikelyFile {
|
|
// Create parent directory if needed
|
|
let parentDir = (basePath as NSString).deletingLastPathComponent
|
|
if !parentDir.isEmpty && parentDir != "/" {
|
|
do {
|
|
try FileManager.default.createDirectory(
|
|
atPath: parentDir,
|
|
withIntermediateDirectories: true,
|
|
attributes: nil
|
|
)
|
|
} catch {
|
|
// Log but don't fail - maybe directory already exists
|
|
// Logger.debug("Could not create parent directory \(parentDir): \(error)")
|
|
}
|
|
}
|
|
|
|
// For fallback mode (invalid screen index that fell back to all screens),
|
|
// always treat as multiple screens to avoid overwriting
|
|
let pathExtension = (basePath as NSString).pathExtension
|
|
let pathWithoutExtension = (basePath as NSString).deletingPathExtension
|
|
|
|
// Extract screen info from fileName (e.g., "screen_1_20250608_120000.png" -> "1_20250608_120000")
|
|
let fileNameWithoutExt = (fileName as NSString).deletingPathExtension
|
|
let screenSuffix = fileNameWithoutExt.replacingOccurrences(of: "screen_", with: "")
|
|
|
|
return "\(pathWithoutExtension)_\(screenSuffix).\(pathExtension)"
|
|
} else {
|
|
// Treat as directory - ensure it exists
|
|
do {
|
|
try FileManager.default.createDirectory(
|
|
atPath: basePath,
|
|
withIntermediateDirectories: true,
|
|
attributes: nil
|
|
)
|
|
} catch {
|
|
// Log but don't fail - maybe directory already exists
|
|
// Logger.debug("Could not create directory \(basePath): \(error)")
|
|
}
|
|
return "\(basePath)/\(fileName)"
|
|
}
|
|
}
|
|
|
|
private static func validatePath(_ path: String) {
|
|
// Check for path traversal attempts
|
|
if path.contains("../") || path.contains("..\\") {
|
|
Logger.shared.debug("Potential path traversal detected in path: \(path)")
|
|
}
|
|
|
|
// Check for system-sensitive paths
|
|
let sensitivePathPrefixes = ["/etc/", "/usr/", "/bin/", "/sbin/", "/System/", "/Library/System/"]
|
|
let normalizedPath = (path as NSString).standardizingPath
|
|
|
|
for prefix in sensitivePathPrefixes where normalizedPath.hasPrefix(prefix) {
|
|
Logger.shared.debug("Path points to system directory: \(path) -> \(normalizedPath)")
|
|
break
|
|
}
|
|
}
|
|
}
|