mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-02-28 00:22:41 -08:00
127 lines
3.5 KiB
Swift
127 lines
3.5 KiB
Swift
import AppKit
|
|
import CoreGraphics
|
|
import Foundation
|
|
|
|
struct Bounds: Encodable {
|
|
let x: Int
|
|
let y: Int
|
|
let width: Int
|
|
let height: Int
|
|
}
|
|
|
|
struct WindowInfo: Encodable {
|
|
let id: Int
|
|
let owner: String
|
|
let name: String
|
|
let layer: Int
|
|
let bounds: Bounds
|
|
let area: Int
|
|
}
|
|
|
|
struct Response: Encodable {
|
|
let count: Int
|
|
let selected: WindowInfo?
|
|
let windows: [WindowInfo]?
|
|
}
|
|
|
|
func value(for flag: String) -> String? {
|
|
guard let idx = CommandLine.arguments.firstIndex(of: flag) else {
|
|
return nil
|
|
}
|
|
let next = CommandLine.arguments.index(after: idx)
|
|
guard next < CommandLine.arguments.endIndex else {
|
|
return nil
|
|
}
|
|
return CommandLine.arguments[next]
|
|
}
|
|
|
|
let frontmostFlag = CommandLine.arguments.contains("--frontmost")
|
|
let explicitApp = value(for: "--app")
|
|
let frontmostName = frontmostFlag ? NSWorkspace.shared.frontmostApplication?.localizedName : nil
|
|
if frontmostFlag && frontmostName == nil {
|
|
fputs("{\"count\":0}\n", stderr)
|
|
exit(1)
|
|
}
|
|
let appFilter = (explicitApp ?? frontmostName)?.lowercased()
|
|
let nameFilter = value(for: "--window-name")?.lowercased()
|
|
let includeList = CommandLine.arguments.contains("--list")
|
|
|
|
let options: CGWindowListOption = [.optionOnScreenOnly, .excludeDesktopElements]
|
|
guard let raw = CGWindowListCopyWindowInfo(options, kCGNullWindowID) as? [[String: Any]] else {
|
|
fputs("{\"count\":0}\n", stderr)
|
|
exit(1)
|
|
}
|
|
|
|
var exactMatches: [WindowInfo] = []
|
|
var partialMatches: [WindowInfo] = []
|
|
exactMatches.reserveCapacity(raw.count)
|
|
partialMatches.reserveCapacity(raw.count)
|
|
|
|
for entry in raw {
|
|
guard let owner = entry[kCGWindowOwnerName as String] as? String else { continue }
|
|
let ownerLower = owner.lowercased()
|
|
if let appFilter, !ownerLower.contains(appFilter) { continue }
|
|
|
|
let name = (entry[kCGWindowName as String] as? String) ?? ""
|
|
if let nameFilter, !name.lowercased().contains(nameFilter) { continue }
|
|
|
|
guard let number = entry[kCGWindowNumber as String] as? Int else { continue }
|
|
let layer = (entry[kCGWindowLayer as String] as? Int) ?? 0
|
|
|
|
guard let boundsDict = entry[kCGWindowBounds as String] as? [String: Any] else { continue }
|
|
let x = Int((boundsDict["X"] as? Double) ?? 0)
|
|
let y = Int((boundsDict["Y"] as? Double) ?? 0)
|
|
let width = Int((boundsDict["Width"] as? Double) ?? 0)
|
|
let height = Int((boundsDict["Height"] as? Double) ?? 0)
|
|
if width <= 0 || height <= 0 { continue }
|
|
|
|
let bounds = Bounds(x: x, y: y, width: width, height: height)
|
|
let area = width * height
|
|
let info = WindowInfo(id: number, owner: owner, name: name, layer: layer, bounds: bounds, area: area)
|
|
if let appFilter, ownerLower == appFilter {
|
|
exactMatches.append(info)
|
|
} else {
|
|
partialMatches.append(info)
|
|
}
|
|
}
|
|
|
|
let windows: [WindowInfo]
|
|
if appFilter != nil && !exactMatches.isEmpty {
|
|
windows = exactMatches
|
|
} else {
|
|
windows = partialMatches
|
|
}
|
|
|
|
func rank(_ window: WindowInfo) -> (Int, Int) {
|
|
// Prefer normal-layer windows, then larger area.
|
|
let layerScore = window.layer == 0 ? 0 : 1
|
|
return (layerScore, -window.area)
|
|
}
|
|
|
|
let ordered: [WindowInfo]
|
|
if frontmostFlag {
|
|
ordered = windows
|
|
} else {
|
|
ordered = windows.sorted { rank($0) < rank($1) }
|
|
}
|
|
let selected = ordered.first
|
|
|
|
let list: [WindowInfo]?
|
|
if includeList {
|
|
list = ordered
|
|
} else {
|
|
list = nil
|
|
}
|
|
|
|
let response = Response(count: windows.count, selected: selected, windows: list)
|
|
let encoder = JSONEncoder()
|
|
encoder.outputFormatting = [.sortedKeys]
|
|
|
|
if let data = try? encoder.encode(response),
|
|
let json = String(data: data, encoding: .utf8) {
|
|
print(json)
|
|
} else {
|
|
fputs("{\"count\":\(windows.count)}\n", stderr)
|
|
exit(1)
|
|
}
|