@@ -23,10 +23,12 @@ import androidx.compose.ui.unit.DpSize
2323import androidx.compose.ui.unit.dp
2424import androidx.compose.ui.window.*
2525import com.kdroid.composetray.lib.linux.LinuxOutsideClickWatcher
26+ import com.kdroid.composetray.utils.debugln
2627import com.kdroid.composetray.lib.mac.MacOSWindowManager
2728import com.kdroid.composetray.lib.mac.MacOutsideClickWatcher
2829import com.kdroid.composetray.lib.mac.MacTrayLoader
2930import com.kdroid.composetray.lib.windows.WindowsOutsideClickWatcher
31+ import com.kdroid.composetray.tray.impl.WindowsTrayInitializer
3032import com.kdroid.composetray.menu.api.TrayMenuBuilder
3133import com.kdroid.composetray.utils.*
3234import io.github.kdroidfilter.platformtools.LinuxDesktopEnvironment
@@ -514,7 +516,12 @@ private fun ApplicationScope.TrayAppImplOriginal(
514516 // Store window reference for macOS Space detection
515517 var windowRef by remember { mutableStateOf< java.awt.Window ? > (null ) }
516518
517- val dialogState = rememberDialogState(size = currentWindowSize)
519+ // Position off-screen initially to prevent flash at wrong position.
520+ // The LaunchedEffect will set the correct position before showing the window.
521+ val dialogState = rememberDialogState(
522+ position = WindowPosition ((- 10000 ).dp, (- 10000 ).dp),
523+ size = currentWindowSize
524+ )
518525 LaunchedEffect (currentWindowSize) { dialogState.size = currentWindowSize }
519526
520527 // Visibility controller for exit-finish observation; content will NOT be disposed.
@@ -556,10 +563,12 @@ private fun ApplicationScope.TrayAppImplOriginal(
556563 windowRef!! .requestFocusInWindow()
557564 }
558565 }
559- return @internalPrimaryAction
566+ } else {
567+ requestHideExplicit()
560568 }
569+ } else {
570+ requestHideExplicit()
561571 }
562- requestHideExplicit()
563572 } else {
564573 if (now - lastHiddenAt >= minHiddenDurationMs) {
565574 if (getOperatingSystem() == WINDOWS && (now - lastFocusLostAt) < 300 ) {
@@ -591,27 +600,47 @@ private fun ApplicationScope.TrayAppImplOriginal(
591600 pendingPosition = null
592601
593602 val position = if (preComputed != null && preComputed !is WindowPosition .PlatformDefault ) {
603+ debugln { " [TrayApp] Using preComputed position: $preComputed " }
594604 preComputed
595605 } else {
596606 // Fallback: poll for position (e.g. initiallyVisible or programmatic show)
597- delay(250 )
607+ // Wait for Windows to finish reorganizing tray icons after adding a new one.
608+ // Windows moves icons around after creation, so we need to wait and re-poll.
609+ debugln { " [TrayApp] No preComputed position, waiting for tray to stabilize..." }
610+ delay(400 ) // Give Windows time to reorganize tray icons
611+
598612 val widthPx = currentWindowSize.width.value.toInt()
599613 val heightPx = currentWindowSize.height.value.toInt()
614+
615+ // On Windows, force a fresh position capture via the native API
616+ if (getOperatingSystem() == WINDOWS ) {
617+ debugln { " [TrayApp] Re-capturing tray position from native API..." }
618+ WindowsTrayInitializer .refreshPosition(tray.instanceKey())
619+ delay(50 ) // Let the position update propagate
620+ }
621+
600622 var pos: WindowPosition = WindowPosition .PlatformDefault
601623 val deadline = System .currentTimeMillis() + 3000
602624 while (pos is WindowPosition .PlatformDefault && System .currentTimeMillis() < deadline) {
603625 pos = getTrayWindowPositionForInstance(
604626 tray.instanceKey(), widthPx, heightPx, horizontalOffset, verticalOffset
605627 )
628+ debugln { " [TrayApp] Polled position: $pos " }
606629 if (pos is WindowPosition .PlatformDefault ) delay(250 )
607630 }
608631 pos
609632 }
633+ debugln { " [TrayApp] Setting dialogState.position = $position " }
610634 dialogState.position = position
611635
636+ // Wait for Compose to apply the position before showing the window
637+ // This prevents the window from flashing at the wrong position
638+ delay(150 ) // Give Compose time to recompose with new position
639+
612640 if (getOperatingSystem() == WINDOWS ) {
613641 autoHideEnabledAt = System .currentTimeMillis() + 1000
614642 }
643+ debugln { " [TrayApp] Now showing window" }
615644 shouldShowWindow = true
616645 lastShownAt = System .currentTimeMillis()
617646 }
@@ -665,12 +694,15 @@ private fun ApplicationScope.TrayAppImplOriginal(
665694 try { window.name = WindowVisibilityMonitor .TRAY_DIALOG_NAME } catch (_: Throwable ) {}
666695 runCatching { WindowVisibilityMonitor .recompute() }
667696
697+ debugln { " [TrayApp] Window shown at native position: x=${window.x} , y=${window.y} , dialogState.position=${dialogState.position} " }
698+
668699 invokeLater {
669700 // Move the popup to the current Space before bringing it to front (macOS)
670701 if (getOperatingSystem() == MACOS ) {
671702 runCatching { MacTrayLoader .lib.tray_set_windows_move_to_active_space() }
672703 runCatching { MacOSWindowManager ().setMoveToActiveSpace(window) }
673704 }
705+ debugln { " [TrayApp] After invokeLater: window at x=${window.x} , y=${window.y} " }
674706 runCatching {
675707 window.toFront()
676708 window.requestFocus()
0 commit comments