Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions maclib/tray.swift
Original file line number Diff line number Diff line change
Expand Up @@ -340,8 +340,10 @@ public func tray_get_status_item_position(
x?.pointee = Int32(lround(rect.midX))

// -- Y ---------------------------------------------------------------
// Inverted coordinate system to match Windows/Linux (origin at top)
let flippedY = Int32(screen.frame.maxY - rect.maxY)
// Convert macOS bottom-origin to AWT top-origin using primary screen height.
// This produces correct global coordinates for all screens (including external).
let primaryHeight = NSScreen.screens.first?.frame.height ?? screen.frame.height
let flippedY = Int32(primaryHeight - rect.maxY)
y?.pointee = flippedY

return 1 // precise coordinates
Expand Down Expand Up @@ -390,7 +392,9 @@ public func tray_get_status_item_position_for(
rect = window.convertToScreen(rect)

x?.pointee = Int32(lround(rect.midX))
let flippedY = Int32(screen.frame.maxY - rect.maxY)
// Convert macOS bottom-origin to AWT top-origin using primary screen height.
let primaryHeight = NSScreen.screens.first?.frame.height ?? screen.frame.height
let flippedY = Int32(primaryHeight - rect.maxY)
y?.pointee = flippedY
return 1
}
Expand Down
54 changes: 32 additions & 22 deletions src/commonMain/kotlin/com/kdroid/composetray/utils/TrayPosition.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import io.github.kdroidfilter.platformtools.LinuxDesktopEnvironment
import io.github.kdroidfilter.platformtools.OperatingSystem
import io.github.kdroidfilter.platformtools.detectLinuxDesktopEnvironment
import io.github.kdroidfilter.platformtools.getOperatingSystem
import java.awt.GraphicsEnvironment
import java.awt.Rectangle
import java.awt.Toolkit
import java.io.File
import java.util.*
Expand All @@ -30,16 +32,16 @@ internal object TrayClickTracker {
Collections.synchronizedMap(mutableMapOf())

fun updateClickPosition(x: Int, y: Int) {
val screenSize = getLogicalScreenSize()
val position = convertPositionToCorner(x, y, screenSize.width, screenSize.height)
val bounds = getScreenBoundsAt(x, y)
val position = convertPositionToCorner(x - bounds.x, y - bounds.y, bounds.width, bounds.height)
val pos = TrayClickPosition(x, y, position)
lastClickPosition.set(pos)
runCatching { saveTrayClickPosition(x, y, position) }
}

fun updateClickPosition(instanceId: String, x: Int, y: Int) {
val screenSize = getLogicalScreenSize()
val position = convertPositionToCorner(x, y, screenSize.width, screenSize.height)
val bounds = getScreenBoundsAt(x, y)
val position = convertPositionToCorner(x - bounds.x, y - bounds.y, bounds.width, bounds.height)
val pos = TrayClickPosition(x, y, position)
perInstancePositions[instanceId] = pos
lastClickPosition.set(pos)
Expand Down Expand Up @@ -71,6 +73,20 @@ private fun getLogicalScreenSize(): java.awt.Dimension {
return Toolkit.getDefaultToolkit().screenSize
}

/**
* Returns the bounds of the screen that contains the given point.
* Falls back to the primary screen if the point is not on any screen.
*/
private fun getScreenBoundsAt(x: Int, y: Int): Rectangle {
val ge = GraphicsEnvironment.getLocalGraphicsEnvironment()
for (gd in ge.screenDevices) {
val bounds = gd.defaultConfiguration.bounds
if (bounds.contains(x, y)) return bounds
}
val primary = Toolkit.getDefaultToolkit().screenSize
return Rectangle(0, 0, primary.width, primary.height)
}

internal fun convertPositionToCorner(x: Int, y: Int, width: Int, height: Int): TrayPosition {
// Use smarter margins based on typical taskbar/panel size
// 100px from edge = probably within taskbar/panel area
Expand Down Expand Up @@ -247,15 +263,13 @@ fun getTrayWindowPosition(
return calculateWindowPositionFromClick(
sx, sy, corner,
windowWidth, windowHeight,
screenSize.width, screenSize.height,
horizontalOffset, verticalOffset
)
}

return calculateWindowPositionFromClick(
posToUse.x, posToUse.y, posToUse.position,
windowWidth, windowHeight,
screenSize.width, screenSize.height,
horizontalOffset, verticalOffset
)
}
Expand All @@ -270,7 +284,6 @@ fun getTrayWindowPosition(
return calculateWindowPositionFromClick(
pos.x, pos.y, pos.position,
windowWidth, windowHeight,
screenSize.width, screenSize.height,
horizontalOffset, verticalOffset
)
}
Expand All @@ -282,7 +295,6 @@ fun getTrayWindowPosition(
return calculateWindowPositionFromClick(
clickPos.x, clickPos.y, clickPos.position,
windowWidth, windowHeight,
screenSize.width, screenSize.height,
horizontalOffset, verticalOffset
)
}
Expand Down Expand Up @@ -314,7 +326,6 @@ fun getTrayWindowPositionForInstance(
verticalOffset: Int = 0
): WindowPosition {
val os = getOperatingSystem()
val screenSize = Toolkit.getDefaultToolkit().screenSize

return when (os) {
OperatingSystem.WINDOWS -> {
Expand All @@ -323,7 +334,6 @@ fun getTrayWindowPositionForInstance(
calculateWindowPositionFromClick(
pos.x, pos.y, pos.position,
windowWidth, windowHeight,
screenSize.width, screenSize.height,
horizontalOffset, verticalOffset
)
}
Expand All @@ -341,12 +351,14 @@ fun getTrayWindowPositionForInstance(
if (precise) {
val regionStr = runCatching { lib.tray_get_status_item_region_for(trayStruct) }.getOrNull()
val trayPos = if (regionStr != null) getMacTrayPosition(regionStr)
else convertPositionToCorner(x, y, screenSize.width, screenSize.height)
else {
val bounds = getScreenBoundsAt(x, y)
convertPositionToCorner(x - bounds.x, y - bounds.y, bounds.width, bounds.height)
}
TrayClickTracker.setClickPosition(instanceId, x, y, trayPos)
return calculateWindowPositionFromClick(
x, y, trayPos,
windowWidth, windowHeight,
screenSize.width, screenSize.height,
horizontalOffset, verticalOffset
)
}
Expand All @@ -360,44 +372,42 @@ fun getTrayWindowPositionForInstance(

/**
* Calcule la position (x,y) depuis un clic précis + applique les offsets et un clamp aux bords écran.
* Uses the screen containing the click point for correct multi-monitor support.
*/
private fun calculateWindowPositionFromClick(
clickX: Int, clickY: Int, trayPosition: TrayPosition,
windowWidth: Int, windowHeight: Int,
screenWidth: Int, screenHeight: Int,
horizontalOffset: Int,
verticalOffset: Int
): WindowPosition {
val os = getOperatingSystem()
val isTop = trayPosition == TrayPosition.TOP_LEFT || trayPosition == TrayPosition.TOP_RIGHT
val isRight = trayPosition == TrayPosition.TOP_RIGHT || trayPosition == TrayPosition.BOTTOM_RIGHT

val sb = getScreenBoundsAt(clickX, clickY)

return if (os == OperatingSystem.WINDOWS) {
// ---- Legacy behavior for Windows (keep exact clickY & raw offsets) ----
var x = clickX - (windowWidth / 2)
var y = if (isTop) clickY else clickY - windowHeight

x += horizontalOffset
y += verticalOffset

if (x < 0) x = 0 else if (x + windowWidth > screenWidth) x = screenWidth - windowWidth
if (y < 0) y = 0 else if (y + windowHeight > screenHeight) y = screenHeight - windowHeight
if (x < sb.x) x = sb.x else if (x + windowWidth > sb.x + sb.width) x = sb.x + sb.width - windowWidth
if (y < sb.y) y = sb.y else if (y + windowHeight > sb.y + sb.height) y = sb.y + sb.height - windowHeight
WindowPosition(x = x.dp, y = y.dp)
} else {
// ---- New behavior for macOS & Linux: snap to bar edge (ignore clickY height within icon) ----
// Conservative guess of panel thickness; works well across GNOME/KDE and typical macOS menubar heights.
val panelGuessPx = 28

var x = clickX - (windowWidth / 2)
val anchorY = if (isTop) panelGuessPx else (screenHeight - panelGuessPx)
val anchorY = if (isTop) sb.y + panelGuessPx else (sb.y + sb.height - panelGuessPx)
var y = if (isTop) anchorY else anchorY - windowHeight

// Direction-aware offsets: always push AWAY from the bar/edge for consistency.
x += if (isRight) -horizontalOffset else horizontalOffset
y += if (isTop) verticalOffset else -verticalOffset

if (x < 0) x = 0 else if (x + windowWidth > screenWidth) x = screenWidth - windowWidth
if (y < 0) y = 0 else if (y + windowHeight > screenHeight) y = screenHeight - windowHeight
if (x < sb.x) x = sb.x else if (x + windowWidth > sb.x + sb.width) x = sb.x + sb.width - windowWidth
if (y < sb.y) y = sb.y else if (y + windowHeight > sb.y + sb.height) y = sb.y + sb.height - windowHeight
WindowPosition(x = x.dp, y = y.dp)
}
}
Expand Down
Loading