Mission Control Turbo: macOS multitasking turbocharged
1#!/usr/bin/env swift
2// Layout test harness — compiles standalone, no Xcode needed.
3// Usage: swift test_layout.swift
4
5import Foundation
6import CoreGraphics
7
8// ─── Minimal model types ───────────────────────────────────────────────
9
10struct WindowInfo {
11 let windowID: CGWindowID
12 let title: String
13 let appName: String
14 let appBundleID: String
15 let frame: CGRect
16 let pid: pid_t
17 let isOnScreen: Bool
18 var thumbnail: CGImage? = nil
19 var appIcon: Any? = nil // NSImage not available without AppKit in script mode
20
21 var id: CGWindowID { windowID }
22 var fingerprintKey: String { "\(appBundleID)|\(title)" }
23}
24
25struct WindowGroup: Identifiable {
26 let appBundleID: String
27 let appName: String
28 var windows: [WindowInfo]
29 var frontmostIndex: Int = Int.max
30 var id: String { appBundleID }
31 var appIcon: Any? { nil }
32}
33
34struct WindowSnapshot {
35 let groups: [WindowGroup]
36 let fingerprint: Set<String>
37 let totalWindowCount: Int
38
39 init(groups: [WindowGroup]) {
40 self.groups = groups
41 self.totalWindowCount = groups.reduce(0) { $0 + $1.windows.count }
42 var fp = Set<String>()
43 for group in groups {
44 for window in group.windows {
45 fp.insert(window.fingerprintKey)
46 }
47 }
48 self.fingerprint = fp
49 }
50}
51
52// ─── Copy of LayoutGeometry ────────────────────────────────────────────
53
54enum LayoutGeometry {
55 static let margin: CGFloat = 24
56 static let groupHeaderHeight: CGFloat = 28
57 static let compactGroupHeaderHeight: CGFloat = 4
58 static let groupSpacing: CGFloat = 8
59 static let itemSpacing: CGFloat = 8
60 static let titleHeight: CGFloat = 22
61 static let minThumbnailWidth: CGFloat = 80
62 static let maxThumbnailWidth: CGFloat = 480
63 static let compactGroupThreshold = 6
64
65 static func effectiveHeaderHeight(groupCount: Int) -> CGFloat {
66 groupCount > compactGroupThreshold ? compactGroupHeaderHeight : groupHeaderHeight
67 }
68
69 static func uniformThumbnailSize(
70 groupWindowCounts: [Int],
71 availableWidth: CGFloat,
72 availableHeight: CGFloat
73 ) -> (size: CGSize, columns: Int) {
74 let groupCount = groupWindowCounts.count
75 guard groupCount > 0 else { return (CGSize(width: 200, height: 150), 1) }
76
77 let headerH = effectiveHeaderHeight(groupCount: groupCount)
78 let headerOverhead = CGFloat(groupCount) * (headerH + groupSpacing)
79 let contentHeight = availableHeight - headerOverhead
80
81 guard contentHeight > 0 else {
82 return (CGSize(width: minThumbnailWidth, height: minThumbnailWidth * 0.625 + titleHeight), 1)
83 }
84
85 func totalRows(cols: Int) -> Int {
86 groupWindowCounts.reduce(0) { $0 + Int(ceil(Double($1) / Double(cols))) }
87 }
88
89 var bestWidth: CGFloat = 0
90 var bestHeight: CGFloat = 0
91 var bestCols = 1
92
93 let maxCols = max(1, Int((availableWidth + itemSpacing) / (minThumbnailWidth + itemSpacing)))
94
95 for cols in 1...maxCols {
96 let thumbWidth = min(maxThumbnailWidth, (availableWidth - CGFloat(cols - 1) * itemSpacing) / CGFloat(cols))
97 guard thumbWidth >= minThumbnailWidth else { continue }
98 let thumbHeight = thumbWidth * 0.625 + titleHeight
99 let rows = totalRows(cols: cols)
100 let neededHeight = CGFloat(rows) * thumbHeight + CGFloat(max(0, rows - 1)) * itemSpacing
101
102 if neededHeight <= contentHeight && thumbWidth > bestWidth {
103 bestWidth = thumbWidth
104 bestHeight = thumbHeight
105 bestCols = cols
106 }
107 }
108
109 if bestWidth == 0 {
110 var lo: CGFloat = 20
111 var hi = min(maxThumbnailWidth, availableWidth)
112
113 for _ in 0..<40 {
114 let mid = (lo + hi) / 2
115 let cols = max(1, Int((availableWidth + itemSpacing) / (mid + itemSpacing)))
116 let thumbHeight = mid * 0.625 + titleHeight
117 let rows = totalRows(cols: cols)
118 let neededHeight = CGFloat(rows) * thumbHeight + CGFloat(max(0, rows - 1)) * itemSpacing
119
120 if neededHeight <= contentHeight {
121 bestWidth = mid
122 bestHeight = thumbHeight
123 bestCols = cols
124 lo = mid
125 } else {
126 hi = mid
127 }
128 }
129
130 if bestWidth == 0 {
131 bestCols = maxCols
132 bestWidth = max(20, (availableWidth - CGFloat(bestCols - 1) * itemSpacing) / CGFloat(bestCols))
133 bestHeight = bestWidth * 0.625 + titleHeight
134 }
135 }
136
137 return (CGSize(width: bestWidth, height: bestHeight), bestCols)
138 }
139}
140
141// ─── Simplified layout engine (no stability/reuse) ─────────────────────
142
143struct GroupLayout {
144 let columns: Int
145 let thumbnailSize: CGSize
146 let itemRange: Range<Int>
147}
148
149struct LayoutItem {
150 let title: String
151 let appName: String
152 let frame: CGRect
153 let groupIndex: Int
154}
155
156struct LayoutGroupHeader {
157 let appName: String
158 let frame: CGRect
159}
160
161struct LayoutResult {
162 let items: [LayoutItem]
163 let headers: [LayoutGroupHeader]
164 let groupLayouts: [GroupLayout]
165 let totalHeight: CGFloat
166 let isCompactHeaders: Bool
167}
168
169func computeLayout(snapshot: WindowSnapshot, screenBounds: CGRect) -> LayoutResult {
170 let availableWidth = screenBounds.width - LayoutGeometry.margin * 2
171 let availableHeight = screenBounds.height - LayoutGeometry.margin * 2
172 let groupCount = snapshot.groups.count
173 let isCompact = groupCount > LayoutGeometry.compactGroupThreshold
174 let headerH = LayoutGeometry.effectiveHeaderHeight(groupCount: groupCount)
175
176 let groupWindowCounts = snapshot.groups.map { $0.windows.count }
177 let (thumbSize, cols) = LayoutGeometry.uniformThumbnailSize(
178 groupWindowCounts: groupWindowCounts,
179 availableWidth: availableWidth,
180 availableHeight: availableHeight
181 )
182
183 var items: [LayoutItem] = []
184 var headers: [LayoutGroupHeader] = []
185 var groupLayouts: [GroupLayout] = []
186
187 var y = screenBounds.origin.y + LayoutGeometry.margin
188
189 for (groupIndex, group) in snapshot.groups.enumerated() {
190 let windowsInRow = min(cols, group.windows.count)
191 let gridWidth = CGFloat(windowsInRow) * thumbSize.width + CGFloat(max(0, windowsInRow - 1)) * LayoutGeometry.itemSpacing
192 let xOffset = screenBounds.origin.x + LayoutGeometry.margin + (availableWidth - gridWidth) / 2
193
194 let headerFrame = CGRect(
195 x: screenBounds.origin.x + LayoutGeometry.margin,
196 y: y,
197 width: availableWidth,
198 height: headerH
199 )
200 headers.append(LayoutGroupHeader(appName: group.appName, frame: headerFrame))
201 y += headerH
202
203 let itemStart = items.count
204
205 for (windowIndex, window) in group.windows.enumerated() {
206 let col = windowIndex % cols
207 let row = windowIndex / cols
208
209 let x = xOffset + CGFloat(col) * (thumbSize.width + LayoutGeometry.itemSpacing)
210 let itemY = y + CGFloat(row) * (thumbSize.height + LayoutGeometry.itemSpacing)
211
212 let frame = CGRect(x: x, y: itemY, width: thumbSize.width, height: thumbSize.height)
213 items.append(LayoutItem(title: window.title, appName: window.appName, frame: frame, groupIndex: groupIndex))
214 }
215
216 let rowsInGroup = Int(ceil(Double(group.windows.count) / Double(cols)))
217 let groupContentHeight = CGFloat(rowsInGroup) * thumbSize.height + CGFloat(max(0, rowsInGroup - 1)) * LayoutGeometry.itemSpacing
218
219 groupLayouts.append(GroupLayout(
220 columns: cols,
221 thumbnailSize: thumbSize,
222 itemRange: itemStart..<items.count
223 ))
224
225 y += groupContentHeight + LayoutGeometry.groupSpacing
226 }
227
228 let totalHeight = y - screenBounds.origin.y
229 return LayoutResult(items: items, headers: headers, groupLayouts: groupLayouts, totalHeight: totalHeight, isCompactHeaders: isCompact)
230}
231
232// ─── Test scenarios ────────────────────────────────────────────────────
233
234func makeWindow(id: Int, title: String, appName: String, bundleID: String) -> WindowInfo {
235 WindowInfo(
236 windowID: CGWindowID(id),
237 title: title,
238 appName: appName,
239 appBundleID: bundleID,
240 frame: CGRect(x: 0, y: 0, width: 800, height: 600),
241 pid: pid_t(id),
242 isOnScreen: true
243 )
244}
245
246func groupWindows(_ windows: [WindowInfo]) -> [WindowGroup] {
247 var dict: [String: WindowGroup] = [:]
248 for (i, w) in windows.enumerated() {
249 if dict[w.appBundleID] == nil {
250 dict[w.appBundleID] = WindowGroup(appBundleID: w.appBundleID, appName: w.appName, windows: [])
251 }
252 dict[w.appBundleID]!.windows.append(w)
253 dict[w.appBundleID]!.frontmostIndex = min(dict[w.appBundleID]!.frontmostIndex, i)
254 }
255 return dict.values.sorted { $0.frontmostIndex < $1.frontmostIndex }
256}
257
258func printResult(_ result: LayoutResult, screenBounds: CGRect, label: String) {
259 print("═══════════════════════════════════════════════════════════════")
260 print("TEST: \(label)")
261 print("Screen: \(Int(screenBounds.width))×\(Int(screenBounds.height))")
262 print("Total height used: \(Int(result.totalHeight)) / \(Int(screenBounds.height))")
263 print("Compact headers: \(result.isCompactHeaders)")
264 print("")
265
266 var allFit = true
267
268 for (gi, gl) in result.groupLayouts.enumerated() {
269 let header = result.headers[gi]
270 print(" Group \(gi): \(header.appName)")
271 print(" Header at y=\(Int(header.frame.minY)), h=\(Int(header.frame.height))")
272 print(" Thumbnail: \(Int(gl.thumbnailSize.width))×\(Int(gl.thumbnailSize.height)), cols=\(gl.columns)")
273
274 for idx in gl.itemRange {
275 let item = result.items[idx]
276 let f = item.frame
277 let withinScreen = f.minX >= screenBounds.minX && f.minY >= screenBounds.minY &&
278 f.maxX <= screenBounds.maxX && f.maxY <= screenBounds.maxY
279 let marker = withinScreen ? "✓" : "✗ OFF-SCREEN"
280 if !withinScreen { allFit = false }
281 print(" [\(idx)] \"\(item.title)\" @ (\(Int(f.minX)),\(Int(f.minY))) \(Int(f.width))×\(Int(f.height)) \(marker)")
282 }
283 }
284
285 // Check uniform sizing within each group
286 for (gi, gl) in result.groupLayouts.enumerated() {
287 let sizes = Set(gl.itemRange.map { idx -> String in
288 let f = result.items[idx].frame
289 return "\(Int(f.width))×\(Int(f.height))"
290 })
291 if sizes.count > 1 {
292 print(" ⚠️ Group \(gi) has NON-UNIFORM sizes: \(sizes)")
293 }
294 }
295
296 // Check sizes are consistent across groups (ideally similar or justified)
297 let allSizes = result.groupLayouts.map { "\(Int($0.thumbnailSize.width))×\(Int($0.thumbnailSize.height))" }
298 let uniqueSizes = Set(allSizes)
299 if uniqueSizes.count > 1 {
300 print("\n ℹ️ Different thumbnail sizes across groups: \(uniqueSizes)")
301 let widths = result.groupLayouts.map { $0.thumbnailSize.width }
302 let ratio = (widths.max() ?? 1) / max(widths.min() ?? 1, 1)
303 if ratio > 3.0 {
304 print(" ⚠️ Size ratio \(String(format: "%.1f", ratio))x between largest and smallest — may look unbalanced")
305 }
306 }
307
308 print("\n All items on screen: \(allFit ? "✓ YES" : "✗ NO")")
309 print("")
310}
311
312// ─── Run tests ─────────────────────────────────────────────────────────
313
314let screen1080 = CGRect(x: 0, y: 0, width: 1920, height: 1080)
315let screen1440 = CGRect(x: 0, y: 0, width: 2560, height: 1440)
316let screenLaptop = CGRect(x: 0, y: 0, width: 1470, height: 956) // 14" MacBook Pro effective
317
318// Test 1: Few windows, few apps
319do {
320 let windows = [
321 makeWindow(id: 1, title: "Document", appName: "Safari", bundleID: "com.apple.Safari"),
322 makeWindow(id: 2, title: "Gmail", appName: "Safari", bundleID: "com.apple.Safari"),
323 makeWindow(id: 3, title: "Main.swift", appName: "Xcode", bundleID: "com.apple.dt.Xcode"),
324 ]
325 let groups = groupWindows(windows)
326 let snapshot = WindowSnapshot(groups: groups)
327 let result = computeLayout(snapshot: snapshot, screenBounds: screen1080)
328 printResult(result, screenBounds: screen1080, label: "3 windows, 2 apps — 1080p")
329}
330
331// Test 2: Many windows, many apps
332do {
333 var windows: [WindowInfo] = []
334 var id = 1
335 for app in ["Safari", "Xcode", "Terminal", "Finder", "Slack", "Discord", "Notes", "Mail"] {
336 let bundleID = "com.test.\(app.lowercased())"
337 let count = app == "Safari" ? 5 : (app == "Xcode" ? 3 : (app == "Terminal" ? 4 : 1))
338 for w in 0..<count {
339 windows.append(makeWindow(id: id, title: "\(app) Window \(w+1)", appName: app, bundleID: bundleID))
340 id += 1
341 }
342 }
343 let groups = groupWindows(windows)
344 let snapshot = WindowSnapshot(groups: groups)
345 let result = computeLayout(snapshot: snapshot, screenBounds: screen1080)
346 printResult(result, screenBounds: screen1080, label: "\(windows.count) windows, 8 apps — 1080p")
347}
348
349// Test 3: Lots of windows on laptop screen
350do {
351 var windows: [WindowInfo] = []
352 var id = 1
353 for app in ["Safari", "Chrome", "Xcode", "Terminal", "Finder", "Slack", "Discord", "Notes", "Mail", "Calendar"] {
354 let bundleID = "com.test.\(app.lowercased())"
355 let count = app == "Safari" ? 6 : (app == "Chrome" ? 4 : (app == "Xcode" ? 3 : 1))
356 for w in 0..<count {
357 windows.append(makeWindow(id: id, title: "\(app) Window \(w+1)", appName: app, bundleID: bundleID))
358 id += 1
359 }
360 }
361 let groups = groupWindows(windows)
362 let snapshot = WindowSnapshot(groups: groups)
363 let result = computeLayout(snapshot: snapshot, screenBounds: screenLaptop)
364 printResult(result, screenBounds: screenLaptop, label: "\(windows.count) windows, 10 apps — Laptop (1470×956)")
365}
366
367// Test 4: Single app, many windows
368do {
369 let windows = (1...12).map { i in
370 makeWindow(id: i, title: "Tab \(i)", appName: "Safari", bundleID: "com.apple.Safari")
371 }
372 let groups = groupWindows(windows)
373 let snapshot = WindowSnapshot(groups: groups)
374 let result = computeLayout(snapshot: snapshot, screenBounds: screen1080)
375 printResult(result, screenBounds: screen1080, label: "12 windows, 1 app — 1080p")
376}
377
378// Test 5: Each app has exactly 1 window
379do {
380 let apps = ["Safari", "Xcode", "Terminal", "Finder", "Slack", "Discord", "Notes", "Mail", "Calendar", "Reminders", "Music", "Photos"]
381 let windows = apps.enumerated().map { (i, app) in
382 makeWindow(id: i + 1, title: "Main", appName: app, bundleID: "com.test.\(app.lowercased())")
383 }
384 let groups = groupWindows(windows)
385 let snapshot = WindowSnapshot(groups: groups)
386 let result = computeLayout(snapshot: snapshot, screenBounds: screenLaptop)
387 printResult(result, screenBounds: screenLaptop, label: "12 windows, 12 apps (1 each) — Laptop")
388}
389
390print("═══════════════════════════════════════════════════════════════")
391print("DONE")