@@ -160,6 +160,9 @@ class LayoutManager: ObservableObject {
160160 }
161161
162162 // 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+
163166 for (index, saved) in filteredLayouts. enumerated ( ) {
164167 guard let savedOwner = saved [ " owner " ] as? String ,
165168 let savedName = saved [ " name " ] as? String ,
@@ -179,9 +182,7 @@ class LayoutManager: ObservableObject {
179182 if app == nil , let bundleId = saved [ " bundleId " ] as? String , !bundleId. isEmpty,
180183 let url = NSWorkspace . shared. urlForApplication ( withBundleIdentifier: bundleId) {
181184 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
185186 let updatedApps = NSWorkspace . shared. runningApplications
186187 app = updatedApps. first ( where: { $0. localizedName == savedOwner } )
187188 }
@@ -263,58 +264,59 @@ class LayoutManager: ObservableObject {
263264 }
264265 }
265266
266- // Apply the layout to the target window
267+ // Collect window for positioning
267268 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) )
310270 windowFound = true
311271 }
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+ }
312283
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
317301 }
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) ' " )
318320 }
319321 }
320322
0 commit comments