@@ -160,6 +160,9 @@ class LayoutManager: ObservableObject {
160
160
}
161
161
162
162
// Use Accessibility API for window manipulation - more reliable than AppleScript
163
+ // First, collect all windows that need to be positioned
164
+ var windowsToPosition : [ ( windowElement: AXUIElement , app: NSRunningApplication , saved: [ String : Any ] , targetWindowTitle: String ) ] = [ ]
165
+
163
166
for (index, saved) in filteredLayouts. enumerated ( ) {
164
167
guard let savedOwner = saved [ " owner " ] as? String ,
165
168
let savedName = saved [ " name " ] as? String ,
@@ -179,9 +182,7 @@ class LayoutManager: ObservableObject {
179
182
if app == nil , let bundleId = saved [ " bundleId " ] as? String , !bundleId. isEmpty,
180
183
let url = NSWorkspace . shared. urlForApplication ( withBundleIdentifier: bundleId) {
181
184
try ? await NSWorkspace . shared. openApplication ( at: url, configuration: NSWorkspace . OpenConfiguration ( ) )
182
- // Wait a bit for the app to launch
183
- try ? await Task . sleep ( nanoseconds: 5_000_000_000 )
184
- // Refresh running apps
185
+ // Refresh running apps immediately
185
186
let updatedApps = NSWorkspace . shared. runningApplications
186
187
app = updatedApps. first ( where: { $0. localizedName == savedOwner } )
187
188
}
@@ -263,58 +264,59 @@ class LayoutManager: ObservableObject {
263
264
}
264
265
}
265
266
266
- // Apply the layout to the target window
267
+ // Collect window for positioning
267
268
if let windowElement = targetWindowElement {
268
- // Get current window position and size to check if it needs to be moved
269
- var currentPosition : CFTypeRef ?
270
- var currentSize : CFTypeRef ?
271
- AXUIElementCopyAttributeValue ( windowElement, kAXPositionAttribute as CFString , & currentPosition)
272
- AXUIElementCopyAttributeValue ( windowElement, kAXSizeAttribute as CFString , & currentSize)
273
-
274
- var currentPos = CGPoint . zero
275
- var currentSz = CGSize . zero
276
-
277
- if let pos = currentPosition {
278
- AXValueGetValue ( pos as! AXValue , . cgPoint, & currentPos)
279
- }
280
- if let sz = currentSize {
281
- AXValueGetValue ( sz as! AXValue , . cgSize, & currentSz)
282
- }
283
-
284
- let targetPosition = CGPoint ( x: x, y: y)
285
- let targetSize = CGSize ( width: width, height: height)
286
-
287
- // Always restore windows to their exact saved positions when loading a layout
288
- // This ensures that manually resized windows get restored to their saved layout
289
- // No tolerance check - always apply the saved layout
290
-
291
- // Set position
292
- var position = targetPosition
293
- let posValue = AXValueCreate ( . cgPoint, & position)
294
- let posResult = AXUIElementSetAttributeValue ( windowElement, kAXPositionAttribute as CFString , posValue!)
295
- if posResult != AXError . success {
296
- continue
297
- }
298
-
299
- // Set size
300
- var size = targetSize
301
- let sizeValue = AXValueCreate ( . cgSize, & size)
302
- let sizeResult = AXUIElementSetAttributeValue ( windowElement, kAXSizeAttribute as CFString , sizeValue!)
303
- if sizeResult != AXError . success {
304
- continue
305
- }
306
-
307
- // Bring window to front
308
- AXUIElementSetAttributeValue ( windowElement, kAXFrontmostAttribute as CFString , kCFBooleanTrue)
309
-
269
+ windowsToPosition. append ( ( windowElement: windowElement, app: app, saved: saved, targetWindowTitle: targetWindowTitle) )
310
270
windowFound = true
311
271
}
272
+ }
273
+
274
+ // Now position all windows first
275
+ for (windowElement, app, saved, targetWindowTitle) in windowsToPosition {
276
+ guard let bounds = saved [ " bounds " ] as? [ String : Any ] ,
277
+ let x = bounds [ " X " ] as? Double ,
278
+ let y = bounds [ " Y " ] as? Double ,
279
+ let width = bounds [ " Width " ] as? Double ,
280
+ let height = bounds [ " Height " ] as? Double else {
281
+ continue
282
+ }
312
283
313
- // Add a small delay between window operations to ensure system stability
314
- // This helps prevent race conditions when multiple windows are being repositioned
315
- if index < filteredLayouts. count - 1 {
316
- try ? await Task . sleep ( nanoseconds: 100_000_000 ) // 100ms delay
284
+ let targetPosition = CGPoint ( x: x, y: y)
285
+ let targetSize = CGSize ( width: width, height: height)
286
+
287
+ // Set position
288
+ var position = targetPosition
289
+ let posValue = AXValueCreate ( . cgPoint, & position)
290
+ let posResult = AXUIElementSetAttributeValue ( windowElement, kAXPositionAttribute as CFString , posValue!)
291
+ if posResult != AXError . success {
292
+ continue
293
+ }
294
+
295
+ // Set size
296
+ var size = targetSize
297
+ let sizeValue = AXValueCreate ( . cgSize, & size)
298
+ let sizeResult = AXUIElementSetAttributeValue ( windowElement, kAXSizeAttribute as CFString , sizeValue!)
299
+ if sizeResult != AXError . success {
300
+ continue
317
301
}
302
+
303
+ print ( " 📐 Positioned window ' \( targetWindowTitle) ' at ( \( x) , \( y) ) with size \( width) x \( height) " )
304
+ }
305
+
306
+ // Now bring windows to front in reverse order (last window becomes frontmost)
307
+ for (windowElement, app, saved, targetWindowTitle) in windowsToPosition. reversed ( ) {
308
+ guard let savedOwner = saved [ " owner " ] as? String else { continue }
309
+
310
+ // Method 1: Activate the application first
311
+ app. activate ( options: [ . activateIgnoringOtherApps] )
312
+
313
+ // Method 2: Use Accessibility API to bring window to front
314
+ AXUIElementSetAttributeValue ( windowElement, kAXFrontmostAttribute as CFString , kCFBooleanTrue)
315
+
316
+ // Method 3: Set as main window
317
+ AXUIElementSetAttributeValue ( windowElement, kAXMainAttribute as CFString , kCFBooleanTrue)
318
+
319
+ print ( " 🎯 Brought window ' \( targetWindowTitle) ' to front for app ' \( savedOwner) ' " )
318
320
}
319
321
}
320
322
0 commit comments