naviapps/window-kit
WindowKit is a Swift package for macOS window models, hit-testing contracts, placement geometry,
Requirements
- macOS 14 or later
- Swift 6.0 or later
Installation
Add this package to your Swift Package dependencies:
.package(url: "https://github.com/naviapps/window-kit.git", from: "2.0.0")Then add the product that matches your use case. Use WindowKit by itself for models, protocols, hit-testing abstractions, and geometry. Add both products when you use live Core Graphics, AppKit, or Accessibility integration:
.product(name: "WindowKit", package: "window-kit"),
.product(name: "WindowKitAppKit", package: "window-kit"),Documentation
Local API catalogs for this checkout:
Basic Usage
Use WindowKit when you need window models, protocols, and geometry:
import CoreGraphics
import WindowKit
let screen = WindowScreen(
frame: CGRect(x: 0, y: 0, width: 1440, height: 900),
visibleFrame: CGRect(x: 0, y: 44, width: 1440, height: 812)
)
let frame = WindowPlacementCalculator.frame(
for: .grid(columns: 2, rows: 1, column: 0, row: 0, columnSpan: 1, rowSpan: 1),
in: screen.visibleFrame,
inset: 8
)Use WindowKitAppKit when you want live macOS integration:
import CoreGraphics
import WindowKit
import WindowKitAppKit
@MainActor
func placeFocusedWindow() throws -> (selected: WindowSnapshot?, topmost: WindowSnapshot?) {
let query = WindowSnapshotQuery()
let snapshots = try query.snapshots()
let firstWindowIdentifier = snapshots.first?.windowIdentifier
let selectedSnapshot = try firstWindowIdentifier.flatMap { try query.snapshot(for: $0) }
let hitTester = WindowHitTester()
let mouseLocation = CGPoint(x: 120, y: 80)
let topmost = hitTester.topmost(at: mouseLocation)
let placer = WindowPlacer()
try placer.place(
.grid(columns: 2, rows: 1, column: 1, row: 0, columnSpan: 1, rowSpan: 1),
for: .focused
)
return (selectedSnapshot, topmost)
}Window snapshots are immutable app-facing models for filtering, hit-testing, placement decisions, and deterministic tests without holding live system references. Snapshots include the process identifier, Core Graphics window identifier, owner metadata, title, and a finite standardized frame. In WindowKitAppKit, WindowSnapshotQuery.snapshots() throws WindowSnapshotQueryError.windowListUnavailable when macOS does not return a readable window list. WindowSnapshotQuery.snapshot(for:) reads a single known Core Graphics window identifier and still applies the query filter. Use WindowSnapshotProviding when host-app services should depend on a mockable snapshot source instead of a concrete query, and WindowSnapshotResolving when they only resolve a known window.
WindowSnapshotFilter supports both include and exclude sets for owner bundle identifiers, owner process identifiers, and window identifiers. Include sets are allow-lists when non-empty; exclude sets are applied after include sets. The AppKit query can also limit snapshots to the first matching entry in the order returned by the macOS window list. Use WindowSnapshotFilter.includes(_:) when host-owned providers need the same deterministic filtering semantics without depending on WindowKitAppKit.
Hit-testing is available through the same small app-facing facade shown above. Use WindowHitTesting when host-app services should depend on a mockable hit-testing source instead of a concrete hit tester.
Responsibility Boundary
WindowKit owns platform-neutral window values, placement calculations, screen selection, and protocol contracts. WindowKitAppKit owns AppKit and Accessibility-backed live adapters.
WindowKit intentionally does not own:
- permission request or onboarding UI
- gesture recognition or input monitoring
- window rule engines, presets, or user preference persistence
- application activation policy
- animation history or app-specific workflow coordination
Those concerns should live in the host app or a package with that direct responsibility.
Placement and Screens
WindowPlacementCalculator clamps insets to the available area. Grid placements clamp invalid columns, rows, and spans into the available grid. Grid columns and rows advance from the selected area's minimum x and y. Row zero is at the bottom in AppKit coordinates and at the top in Accessibility coordinates. Centered placements clamp width and height ratios to 0...1.
WindowScreen standardizes full and visible frames at construction. WindowScreenTarget.containingWindow selects the screen containing the target window center. WindowKit screen-selection primitives support forgiving fallback semantics so host apps can decide whether a missing screen is recoverable or exceptional. Use WindowScreenSelector when custom host-app controllers need the same deterministic screen ordering and target resolution as WindowPlacer; selector APIs order input screen lists before applying ordered-index and fallback selection. Use WindowScreenMovement when custom host-app controllers need the same finite, deterministic screen-to-screen movement geometry without using live Accessibility control.
In WindowKitAppKit, WindowPlacer uses forgiving screen selection by default. When screens exist but a requested point, display identifier, ordered index, or current window screen cannot be matched, placement and screen moves fall back instead of throwing. An empty screen list always throws. Pass screenFallback: .none to WindowPlacer.place or WindowPlacer.moveToAdjacentScreen when a missing screen should throw WindowPlacerError.screenNotFound.
The default WindowPlacer() converts AppKit screen frames to the top-left-origin Accessibility coordinate space used by AXWindowController. Grid row zero is therefore the top row, and a display above the primary display has negative y coordinates. Point screen targets use that same space. NSScreenProvider itself retains AppKit frames; when injecting custom providers into a placer, ensure window frames, screen frames, and points already share one coordinate space. Screen ordering follows increasing x, then increasing y in that space.
Accessibility
WindowKitAppKit uses macOS Accessibility APIs for live window control. Host apps are responsible for declaring and guiding the required permissions, handling denied access, and choosing when to prompt users.
Window discovery, querying, and hit-testing read window metadata returned by macOS, including process identifiers, app names, bundle identifiers, window titles, and frames for standard-layer windows. Host apps are responsible for their own privacy disclosures, logging choices, and any Screen Recording or Accessibility permission flows needed by their product.
Raw AX resolver details are implementation details. The public Accessibility-backed AXWindowController surfaces app-facing failures through WindowAccessibilityError, with application and window resolution failures preserving the WindowTarget that could not be resolved. Each failure includes a WindowAccessibilityOperation; underlying failures are further classified by WindowAccessibilityOperationFailure instead of raw AX error values. When resolving a known window identifier, invalidated elements in the application's window list are skipped so a closed sibling does not prevent finding the target. If no live element matches, the target is unavailable. Other lookup failures still propagate; the controller does not retry or fall back to the focused window. Unrecognized future AXError values are surfaced as WindowAccessibilityOperationFailure.unrecognizedAXError. AX success reported through a failure path is surfaced as WindowAccessibilityOperationFailure.unexpectedSuccess instead of exposing success as a failure. Unmatched window role metadata is represented as WindowRole.unclassified(accessibilityRole:accessibilitySubrole:) so host apps can inspect the raw Accessibility role labels without treating them as a known classification. Role-unavailable values are not classified from subrole metadata alone. Host apps should depend on the WindowKit protocols and construct the public WindowKitAppKit adapters only at their live system boundary.
The package does not request permissions, collect analytics, transmit data, or persist window data by itself.
Repository guide
Package.swift defines products, target boundaries, platform requirements, and dependency resolution.
| Target | Owns | Implementation and tests | | --- | --- | --- | | WindowKit | Snapshots, filters, geometry, screen selection and protocols | Source · Tests | | WindowKitAppKit | CG/AppKit/Accessibility queries and live window control | Source · Tests |
Development
make checkThe gate runs strict Swift formatting, Debug and Release builds, and the test suite with complete concurrency checking and warnings as errors. CI runs the same gate. On macOS, XCTest and DocC validation require full Xcode developer tools. If the active developer directory is Command Line Tools, set DEVELOPER_DIR to the installed Xcode developer directory for the check command.
See AGENTS.md for change guidance and CONTRIBUTING.md for contribution policy. Release history and private reporting stay in CHANGELOG.md and SECURITY.md.
Contributing
See CONTRIBUTING.md. Release notes are in CHANGELOG.md.
Security
Report vulnerabilities privately. See SECURITY.md.
License
WindowKit is released under the MIT License. See LICENSE.
Package Metadata
Repository: naviapps/window-kit
Default branch: main
README: README.md