diff --git a/maclib/tray.swift b/maclib/tray.swift index 7666c17f..fc28f139 100644 --- a/maclib/tray.swift +++ b/maclib/tray.swift @@ -412,4 +412,16 @@ public func tray_get_status_item_region_for( let midX = screen.frame.midX let region = rect.minX < midX ? "top-left" : "top-right" return strdup(region) +} + +// MARK: - Spaces / Virtual Desktop support + +/// Sets NSWindowCollectionBehavior.moveToActiveSpace on all app windows +/// so that showing a window moves it to the current Space instead of +/// switching back to the Space where it was originally created. +@_cdecl("tray_set_windows_move_to_active_space") +public func tray_set_windows_move_to_active_space() { + for window in NSApp.windows { + window.collectionBehavior.insert(.moveToActiveSpace) + } } \ No newline at end of file diff --git a/src/commonMain/kotlin/com/kdroid/composetray/lib/mac/MacOsWindowManager.kt b/src/commonMain/kotlin/com/kdroid/composetray/lib/mac/MacOsWindowManager.kt index bd79d053..95cf5222 100644 --- a/src/commonMain/kotlin/com/kdroid/composetray/lib/mac/MacOsWindowManager.kt +++ b/src/commonMain/kotlin/com/kdroid/composetray/lib/mac/MacOsWindowManager.kt @@ -29,18 +29,6 @@ interface Foundation : Library { class MacOSWindowManager { - companion object { - // Constants for NSApplication activation policies - const val NSApplicationActivationPolicyRegular = 0L - const val NSApplicationActivationPolicyAccessory = 1L - const val NSApplicationActivationPolicyProhibited = 2L - - // Constants for window levels - const val NSNormalWindowLevel = 0L - const val NSFloatingWindowLevel = 3L - const val NSModalPanelWindowLevel = 8L - } - // Detect platform once private val isMacOs: Boolean = getOperatingSystem() == OperatingSystem.MACOS @@ -206,5 +194,51 @@ class MacOSWindowManager { return getNSApplication() != null } + /** + * Configure an AWT window so that macOS moves it to the active Space + * when it is ordered front, instead of switching back to the Space + * where the window was originally created. + */ + fun setMoveToActiveSpace(awtWindow: java.awt.Window): Boolean { + if (!isMacOs) return false + val localObjc = objc ?: return false + return try { + val viewPtr = Native.getComponentID(awtWindow) + if (viewPtr == 0L) return false + + val nsView = Pointer(viewPtr) + val windowSel = localObjc.sel_registerName("window") + val nsWindow = localObjc.objc_msgSend(nsView, windowSel) + if (nsWindow == Pointer.NULL) return false + + // Read current collectionBehavior and add moveToActiveSpace (1 << 1) + val getCollSel = localObjc.sel_registerName("collectionBehavior") + val current = Pointer.nativeValue(localObjc.objc_msgSend(nsWindow, getCollSel)) + val setCollSel = localObjc.sel_registerName("setCollectionBehavior:") + localObjc.objc_msgSend(nsWindow, setCollSel, current or NSWindowCollectionBehaviorMoveToActiveSpace) + + debugln { "Window configured to move to active Space" } + true + } catch (e: Throwable) { + debugln { "Failed to set moveToActiveSpace: ${e.message}" } + false + } + } + + companion object { + // Constants for NSApplication activation policies + const val NSApplicationActivationPolicyRegular = 0L + const val NSApplicationActivationPolicyAccessory = 1L + const val NSApplicationActivationPolicyProhibited = 2L + + // Constants for window levels + const val NSNormalWindowLevel = 0L + const val NSFloatingWindowLevel = 3L + const val NSModalPanelWindowLevel = 8L + + // NSWindowCollectionBehavior + const val NSWindowCollectionBehaviorMoveToActiveSpace = 2L // 1 << 1 + } + } diff --git a/src/commonMain/kotlin/com/kdroid/composetray/lib/mac/MacTrayManager.kt b/src/commonMain/kotlin/com/kdroid/composetray/lib/mac/MacTrayManager.kt index 96f05422..f35f8c98 100644 --- a/src/commonMain/kotlin/com/kdroid/composetray/lib/mac/MacTrayManager.kt +++ b/src/commonMain/kotlin/com/kdroid/composetray/lib/mac/MacTrayManager.kt @@ -349,6 +349,8 @@ internal class MacTrayManager( @JvmStatic external fun tray_get_status_item_region(): String? @JvmStatic external fun tray_get_status_item_region_for(tray: MacTray): String? + + @JvmStatic external fun tray_set_windows_move_to_active_space() } // Structure for a menu item diff --git a/src/commonMain/kotlin/com/kdroid/composetray/tray/api/TrayApp.kt b/src/commonMain/kotlin/com/kdroid/composetray/tray/api/TrayApp.kt index fa3293ff..a9f9bf04 100644 --- a/src/commonMain/kotlin/com/kdroid/composetray/tray/api/TrayApp.kt +++ b/src/commonMain/kotlin/com/kdroid/composetray/tray/api/TrayApp.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.window.* import com.kdroid.composetray.lib.linux.LinuxOutsideClickWatcher import com.kdroid.composetray.lib.mac.MacOSWindowManager import com.kdroid.composetray.lib.mac.MacOutsideClickWatcher +import com.kdroid.composetray.lib.mac.MacTrayLoader import com.kdroid.composetray.lib.windows.WindowsOutsideClickWatcher import com.kdroid.composetray.menu.api.TrayMenuBuilder import com.kdroid.composetray.utils.* @@ -620,6 +621,11 @@ private fun ApplicationScope.TrayAppImplOriginal( runCatching { WindowVisibilityMonitor.recompute() } invokeLater { + // Move the popup to the current Space before bringing it to front (macOS) + if (getOperatingSystem() == MACOS) { + runCatching { MacTrayLoader.lib.tray_set_windows_move_to_active_space() } + runCatching { MacOSWindowManager().setMoveToActiveSpace(window) } + } runCatching { window.toFront() window.requestFocus()