@@ -203,24 +203,164 @@ class MacOSWindowManager {
203203 if (! isMacOs) return false
204204 val localObjc = objc ? : return false
205205 return try {
206+ // Try direct approach via Native.getComponentID
206207 val viewPtr = Native .getComponentID(awtWindow)
207- if (viewPtr == 0L ) return false
208+ debugln { " [MacOSWindowManager] setMoveToActiveSpace: viewPtr=$viewPtr " }
209+ if (viewPtr != 0L ) {
210+ val nsView = Pointer (viewPtr)
211+ val windowSel = localObjc.sel_registerName(" window" )
212+ val nsWindow = localObjc.objc_msgSend(nsView, windowSel)
213+ if (nsWindow != Pointer .NULL ) {
214+ applySpaceBehavior(localObjc, nsWindow)
215+ return true
216+ }
217+ }
218+
219+ // Fallback: iterate NSApp windows and set on floating-level windows
220+ // (tray popup uses alwaysOnTop=true which sets a floating window level)
221+ debugln { " [MacOSWindowManager] Fallback: searching NSApp windows for floating window..." }
222+ val nsApp = getNSApplication() ? : return false
223+
224+ val windowsSel = localObjc.sel_registerName(" windows" )
225+ val windowsArray = localObjc.objc_msgSend(nsApp, windowsSel)
226+ val countSel = localObjc.sel_registerName(" count" )
227+ val count = Pointer .nativeValue(localObjc.objc_msgSend(windowsArray, countSel)).toInt()
228+ debugln { " [MacOSWindowManager] Found $count NSWindows" }
229+
230+ val objectAtIndexSel = localObjc.sel_registerName(" objectAtIndex:" )
231+ val levelSel = localObjc.sel_registerName(" level" )
232+
233+ var applied = false
234+ for (i in 0 until count) {
235+ val nsWindow = localObjc.objc_msgSend(windowsArray, objectAtIndexSel, i.toLong())
236+ val level = Pointer .nativeValue(localObjc.objc_msgSend(nsWindow, levelSel))
237+ debugln { " [MacOSWindowManager] Window[$i ]: level=$level " }
238+ // Floating windows have level > 0 (NSFloatingWindowLevel = 3)
239+ if (level > 0 ) {
240+ applySpaceBehavior(localObjc, nsWindow)
241+ applied = true
242+ }
243+ }
244+ applied
245+ } catch (e: Throwable ) {
246+ debugln { " Failed to set moveToActiveSpace: ${e.message} " }
247+ false
248+ }
249+ }
250+
251+ private fun applySpaceBehavior (localObjc : ObjectiveC , nsWindow : Pointer ) {
252+ val getCollSel = localObjc.sel_registerName(" collectionBehavior" )
253+ val current = Pointer .nativeValue(localObjc.objc_msgSend(nsWindow, getCollSel))
254+ // Ensure moveToActiveSpace is set (moves window to active Space when ordered front)
255+ val desired = (current and NSWindowCollectionBehaviorCanJoinAllSpaces .inv ()) or NSWindowCollectionBehaviorMoveToActiveSpace
256+ if (current != desired) {
257+ debugln { " [MacOSWindowManager] collectionBehavior before=$current , desired=$desired " }
258+ val setCollSel = localObjc.sel_registerName(" setCollectionBehavior:" )
259+ localObjc.objc_msgSend(nsWindow, setCollSel, desired)
260+ val after = Pointer .nativeValue(localObjc.objc_msgSend(nsWindow, getCollSel))
261+ debugln { " [MacOSWindowManager] collectionBehavior after=$after " }
262+ }
263+ debugln { " Window configured with moveToActiveSpace" }
264+ }
265+
266+ /* *
267+ * Check if any floating-level NSWindow is on the active Space.
268+ * Uses NSApp.windows iteration (same fallback as setMoveToActiveSpace).
269+ * Returns true if on active Space or if check fails (fail-open).
270+ */
271+ fun isFloatingWindowOnActiveSpace (): Boolean {
272+ if (! isMacOs) return true
273+ val localObjc = objc ? : return true
274+ return try {
275+ val nsApp = getNSApplication() ? : return true
276+
277+ val windowsSel = localObjc.sel_registerName(" windows" )
278+ val windowsArray = localObjc.objc_msgSend(nsApp, windowsSel)
279+ val countSel = localObjc.sel_registerName(" count" )
280+ val count = Pointer .nativeValue(localObjc.objc_msgSend(windowsArray, countSel)).toInt()
281+
282+ val objectAtIndexSel = localObjc.sel_registerName(" objectAtIndex:" )
283+ val levelSel = localObjc.sel_registerName(" level" )
284+ val isOnActiveSpaceSel = localObjc.sel_registerName(" isOnActiveSpace" )
285+
286+ for (i in 0 until count) {
287+ val nsWindow = localObjc.objc_msgSend(windowsArray, objectAtIndexSel, i.toLong())
288+ val level = Pointer .nativeValue(localObjc.objc_msgSend(nsWindow, levelSel))
289+ if (level > 0 ) {
290+ val onActiveSpace = Pointer .nativeValue(localObjc.objc_msgSend(nsWindow, isOnActiveSpaceSel)) != 0L
291+ debugln { " [MacOSWindowManager] Floating window level=$level , isOnActiveSpace=$onActiveSpace " }
292+ return onActiveSpace
293+ }
294+ }
295+ true // No floating window found, assume on active Space
296+ } catch (e: Throwable ) {
297+ debugln { " Failed to check isOnActiveSpace: ${e.message} " }
298+ true
299+ }
300+ }
301+
302+ /* *
303+ * Check if an AWT window is currently on the active macOS Space.
304+ * Returns true if on the active Space, false if on another Space.
305+ * Returns true by default if the check cannot be performed (fail-open).
306+ */
307+ fun isOnActiveSpace (awtWindow : java.awt.Window ): Boolean {
308+ if (! isMacOs) return true
309+ val localObjc = objc ? : return true
310+ return try {
311+ val viewPtr = Native .getComponentID(awtWindow)
312+ if (viewPtr == 0L ) return true
208313
209314 val nsView = Pointer (viewPtr)
210315 val windowSel = localObjc.sel_registerName(" window" )
211316 val nsWindow = localObjc.objc_msgSend(nsView, windowSel)
212- if (nsWindow == Pointer .NULL ) return false
317+ if (nsWindow == Pointer .NULL ) return true
213318
214- // Read current collectionBehavior and add moveToActiveSpace (1 << 1)
215- val getCollSel = localObjc.sel_registerName(" collectionBehavior" )
216- val current = Pointer .nativeValue(localObjc.objc_msgSend(nsWindow, getCollSel))
217- val setCollSel = localObjc.sel_registerName(" setCollectionBehavior:" )
218- localObjc.objc_msgSend(nsWindow, setCollSel, current or NSWindowCollectionBehaviorMoveToActiveSpace )
319+ val isOnActiveSpaceSel = localObjc.sel_registerName(" isOnActiveSpace" )
320+ val result = Pointer .nativeValue(localObjc.objc_msgSend(nsWindow, isOnActiveSpaceSel))
321+ result != 0L
322+ } catch (e: Throwable ) {
323+ debugln { " Failed to check isOnActiveSpace: ${e.message} " }
324+ true // fail-open: assume on active Space
325+ }
326+ }
219327
220- debugln { " Window configured to move to active Space" }
221- true
328+ /* *
329+ * Bring the floating-level NSWindow to the front on the active Space.
330+ * With moveToActiveSpace collection behavior, this physically moves the window.
331+ * Also activates the application to ensure focus is gained.
332+ */
333+ fun bringFloatingWindowToFront (): Boolean {
334+ if (! isMacOs) return false
335+ val localObjc = objc ? : return false
336+ return try {
337+ val nsApp = getNSApplication() ? : return false
338+
339+ // Activate the app so it can receive focus
340+ val activateSel = localObjc.sel_registerName(" activateIgnoringOtherApps:" )
341+ localObjc.objc_msgSend(nsApp, activateSel, 1L )
342+
343+ val windowsSel = localObjc.sel_registerName(" windows" )
344+ val windowsArray = localObjc.objc_msgSend(nsApp, windowsSel)
345+ val countSel = localObjc.sel_registerName(" count" )
346+ val count = Pointer .nativeValue(localObjc.objc_msgSend(windowsArray, countSel)).toInt()
347+
348+ val objectAtIndexSel = localObjc.sel_registerName(" objectAtIndex:" )
349+ val levelSel = localObjc.sel_registerName(" level" )
350+ val makeKeyAndOrderFrontSel = localObjc.sel_registerName(" makeKeyAndOrderFront:" )
351+
352+ for (i in 0 until count) {
353+ val nsWindow = localObjc.objc_msgSend(windowsArray, objectAtIndexSel, i.toLong())
354+ val level = Pointer .nativeValue(localObjc.objc_msgSend(nsWindow, levelSel))
355+ if (level > 0 ) {
356+ debugln { " [MacOSWindowManager] bringFloatingWindowToFront: level=$level " }
357+ localObjc.objc_msgSend(nsWindow, makeKeyAndOrderFrontSel, Pointer .NULL )
358+ return true
359+ }
360+ }
361+ false
222362 } catch (e: Throwable ) {
223- debugln { " Failed to set moveToActiveSpace : ${e.message} " }
363+ debugln { " Failed to bringFloatingWindowToFront : ${e.message} " }
224364 false
225365 }
226366 }
@@ -237,6 +377,7 @@ class MacOSWindowManager {
237377 const val NSModalPanelWindowLevel = 8L
238378
239379 // NSWindowCollectionBehavior
380+ const val NSWindowCollectionBehaviorCanJoinAllSpaces = 1L // 1 << 0
240381 const val NSWindowCollectionBehaviorMoveToActiveSpace = 2L // 1 << 1
241382 }
242383
0 commit comments