mirror of
https://github.com/samsonjs/Peekaboo.git
synced 2026-03-25 09:25:47 +00:00
- Update to swift-tools-version 6.0 and enable StrictConcurrency - Make all data models and types Sendable for concurrency safety - Migrate commands from ParsableCommand to AsyncParsableCommand - Remove AsyncUtils.swift and synchronous bridging patterns - Update WindowBounds property names to snake_case for consistency - Ensure all error types conform to Sendable protocol - Add comprehensive Swift 6 migration documentation This migration enables full Swift 6 concurrency checking and data race safety while maintaining backward compatibility with the existing API. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
124 lines
4.4 KiB
Swift
124 lines
4.4 KiB
Swift
import AppKit
|
|
import CoreGraphics
|
|
import Foundation
|
|
|
|
final class WindowManager: Sendable {
|
|
static func getWindowsForApp(pid: pid_t, includeOffScreen: Bool = false) throws(WindowError) -> [WindowData] {
|
|
// Logger.shared.debug("Getting windows for PID: \(pid)")
|
|
|
|
// In CI environment, return empty array to avoid accessing window server
|
|
if ProcessInfo.processInfo.environment["CI"] == "true" {
|
|
return []
|
|
}
|
|
|
|
let windowList = try fetchWindowList(includeOffScreen: includeOffScreen)
|
|
let windows = extractWindowsForPID(pid, from: windowList)
|
|
|
|
// Logger.shared.debug("Found \(windows.count) windows for PID \(pid)")
|
|
return windows.sorted { $0.windowIndex < $1.windowIndex }
|
|
}
|
|
|
|
private static func fetchWindowList(includeOffScreen: Bool) throws(WindowError) -> [[String: Any]] {
|
|
let options: CGWindowListOption = includeOffScreen
|
|
? [.excludeDesktopElements]
|
|
: [.excludeDesktopElements, .optionOnScreenOnly]
|
|
|
|
guard let windowList = CGWindowListCopyWindowInfo(options, kCGNullWindowID) as? [[String: Any]] else {
|
|
throw WindowError.windowListFailed
|
|
}
|
|
|
|
return windowList
|
|
}
|
|
|
|
private static func extractWindowsForPID(_ pid: pid_t, from windowList: [[String: Any]]) -> [WindowData] {
|
|
var windows: [WindowData] = []
|
|
var windowIndex = 0
|
|
|
|
for windowInfo in windowList {
|
|
if let window = parseWindowInfo(windowInfo, targetPID: pid, index: windowIndex) {
|
|
windows.append(window)
|
|
windowIndex += 1
|
|
}
|
|
}
|
|
|
|
return windows
|
|
}
|
|
|
|
private static func parseWindowInfo(_ info: [String: Any], targetPID: pid_t, index: Int) -> WindowData? {
|
|
guard let windowPID = info[kCGWindowOwnerPID as String] as? Int32,
|
|
windowPID == targetPID,
|
|
let windowID = info[kCGWindowNumber as String] as? CGWindowID else {
|
|
return nil
|
|
}
|
|
|
|
let title = info[kCGWindowName as String] as? String ?? "Untitled"
|
|
let bounds = extractWindowBounds(from: info)
|
|
let isOnScreen = info[kCGWindowIsOnscreen as String] as? Bool ?? true
|
|
|
|
return WindowData(
|
|
windowId: windowID,
|
|
title: title,
|
|
bounds: bounds,
|
|
isOnScreen: isOnScreen,
|
|
windowIndex: index
|
|
)
|
|
}
|
|
|
|
private static func extractWindowBounds(from windowInfo: [String: Any]) -> CGRect {
|
|
guard let boundsDict = windowInfo[kCGWindowBounds as String] as? [String: Any] else {
|
|
return .zero
|
|
}
|
|
|
|
let xCoordinate = boundsDict["X"] as? Double ?? 0
|
|
let yCoordinate = boundsDict["Y"] as? Double ?? 0
|
|
let width = boundsDict["Width"] as? Double ?? 0
|
|
let height = boundsDict["Height"] as? Double ?? 0
|
|
|
|
return CGRect(x: xCoordinate, y: yCoordinate, width: width, height: height)
|
|
}
|
|
|
|
static func getWindowsInfoForApp(
|
|
pid: pid_t,
|
|
includeOffScreen: Bool = false,
|
|
includeBounds: Bool = false,
|
|
includeIDs: Bool = false
|
|
) throws(WindowError) -> [WindowInfo] {
|
|
let windowDataArray = try getWindowsForApp(pid: pid, includeOffScreen: includeOffScreen)
|
|
|
|
return windowDataArray.map { windowData in
|
|
WindowInfo(
|
|
window_title: windowData.title,
|
|
window_id: includeIDs ? windowData.windowId : nil,
|
|
window_index: windowData.windowIndex,
|
|
bounds: includeBounds ? WindowBounds(
|
|
x_coordinate: Int(windowData.bounds.origin.x),
|
|
y_coordinate: Int(windowData.bounds.origin.y),
|
|
width: Int(windowData.bounds.size.width),
|
|
height: Int(windowData.bounds.size.height)
|
|
) : nil,
|
|
is_on_screen: includeOffScreen ? windowData.isOnScreen : nil
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Extension to add the getWindowsForApp function to ImageCommand
|
|
extension ImageCommand {
|
|
func getWindowsForApp(pid: pid_t) throws(WindowError) -> [WindowData] {
|
|
try WindowManager.getWindowsForApp(pid: pid)
|
|
}
|
|
}
|
|
|
|
enum WindowError: Error, LocalizedError, Sendable {
|
|
case windowListFailed
|
|
case noWindowsFound
|
|
|
|
var errorDescription: String? {
|
|
switch self {
|
|
case .windowListFailed:
|
|
"Failed to get window list from system"
|
|
case .noWindowsFound:
|
|
"No windows found for the specified application"
|
|
}
|
|
}
|
|
}
|