Sources/Miri/WindowRestorer.swift
import AppKit
import ApplicationServices
import MiriCore
/// Moves stranded windows back into view through the Accessibility API, independently of the engine.
enum WindowRestorer {
struct Summary: Codable, Equatable {
var checked = 0
var moved = 0
}
/// Each display's visible frame in Accessibility coordinates, with the main display first.
static func displayFrames() -> [CGRect] {
guard let main = NSScreen.screens.first else { return [] }
return NSScreen.screens.map { screen in
let frame = screen.visibleFrame
return CGRect(x: frame.minX, y: main.frame.maxY - frame.maxY, width: frame.width, height: frame.height)
}
}
/// Recovers every stranded standard window of ordinary apps. `processes` limits recovery to those processes.
@discardableResult
static func recover(processes: Set<pid_t>? = nil) -> Summary {
guard AXIsProcessTrusted() else { return Summary() }
let displays = displayFrames()
var summary = Summary()
for app in NSWorkspace.shared.runningApplications
where app.activationPolicy == .regular && app.processIdentifier != getpid()
&& processes?.contains(app.processIdentifier) ?? true
{
for window in windows(of: app.processIdentifier) {
guard let current = frame(of: window) else { continue }
summary.checked += 1
guard let target = WindowRecovery.target(for: current, displays: displays) else { continue }
setFrame(target, of: window)
summary.moved += 1
}
}
return summary
}
/// The titles of every open window of ordinary apps, so diagnostics can remove them.
static func openWindowTitles() -> [String] {
guard AXIsProcessTrusted() else { return [] }
return NSWorkspace.shared.runningApplications.filter { $0.activationPolicy == .regular }.flatMap { app in
let application = AXUIElementCreateApplication(app.processIdentifier)
AXUIElementSetMessagingTimeout(application, 0.25)
let windows = attribute(kAXWindowsAttribute, of: application) as? [AXUIElement] ?? []
return windows.compactMap { attribute(kAXTitleAttribute, of: $0) as? String }
}
}
/// Standard windows that are neither minimized nor in native fullscreen.
static func windows(of pid: pid_t) -> [AXUIElement] {
let application = AXUIElementCreateApplication(pid)
AXUIElementSetMessagingTimeout(application, 0.25)
guard let windows = attribute(kAXWindowsAttribute, of: application) as? [AXUIElement] else { return [] }
return windows.filter { window in
attribute(kAXSubroleAttribute, of: window) as? String == kAXStandardWindowSubrole
&& attribute(kAXMinimizedAttribute, of: window) as? Bool != true
&& attribute("AXFullScreen", of: window) as? Bool != true
}
}
static func frame(of window: AXUIElement) -> CGRect? {
var origin = CGPoint.zero
var size = CGSize.zero
guard let position = attribute(kAXPositionAttribute, of: window), CFGetTypeID(position) == AXValueGetTypeID(),
let extent = attribute(kAXSizeAttribute, of: window), CFGetTypeID(extent) == AXValueGetTypeID(),
AXValueGetValue(position as! AXValue, .cgPoint, &origin),
AXValueGetValue(extent as! AXValue, .cgSize, &size)
else { return nil }
return CGRect(origin: origin, size: size)
}
/// Sets size, then position, then size again, because an app may limit the size at the old position.
static func setFrame(_ frame: CGRect, of window: AXUIElement) {
var origin = frame.origin
var size = frame.size
guard let position = AXValueCreate(.cgPoint, &origin), let extent = AXValueCreate(.cgSize, &size) else {
return
}
AXUIElementSetAttributeValue(window, kAXSizeAttribute as CFString, extent)
AXUIElementSetAttributeValue(window, kAXPositionAttribute as CFString, position)
AXUIElementSetAttributeValue(window, kAXSizeAttribute as CFString, extent)
}
private static func attribute(_ name: String, of element: AXUIElement) -> CFTypeRef? {
var value: CFTypeRef?
return AXUIElementCopyAttributeValue(element, name as CFString, &value) == .success ? value : nil
}
}
/// Strands one window of a test fixture off-screen, recovers it, and reports the frames. It touches no other app.
enum RecoverySelftest {
struct Report: Encodable {
let schemaVersion = 1
let trusted: Bool
var original: CGRect?
var stranded: CGRect?
var strandedNeededRecovery: Bool?
var recovered: CGRect?
var recoveredIsReachable: Bool?
var summary: WindowRestorer.Summary?
var failure: String?
}
static func run(targetPid: pid_t) -> Report {
var report = Report(trusted: AXIsProcessTrusted())
guard report.trusted else { return report }
let displays = WindowRestorer.displayFrames()
guard let window = WindowRestorer.windows(of: targetPid).first,
let original = WindowRestorer.frame(of: window)
else {
report.failure = String(localized: "The fixture has no standard window.")
return report
}
report.original = original
let farRight = displays.map(\.maxX).max() ?? 0
WindowRestorer.setFrame(original.offsetBy(dx: farRight + 2000 - original.minX, dy: 0), of: window)
report.stranded = WindowRestorer.frame(of: window)
report.strandedNeededRecovery = report.stranded.map {
WindowRecovery.target(for: $0, displays: displays) != nil
}
report.summary = WindowRestorer.recover(processes: [targetPid])
report.recovered = WindowRestorer.frame(of: window)
report.recoveredIsReachable = report.recovered.map { WindowRecovery.target(for: $0, displays: displays) == nil }
return report
}
}