Determinite position (x, y) of specific system tray icon

251 Views Asked by At

Question

I'm working on a desktop application using Jetbrains Compose, but I'm open to solutions using Swing (it's used under the hood) as well. I want to implement a feature where the application window is initially located at the system tray icon, similar to how JetBrains Toolbox behaves (I want to place an (undecorated) window close to the tray icon).

Examples

screenshot example enter image description here

I have tried using mouse events to detect clicks on the tray icon and position the window accordingly, but that approach doesn't achieve the desired behavior. It actually works, but window sometimes is jumping (depending on where I clicked and solution overall looks weird).

enter image description here

My previous solution

I publish my solution for case someone need it:

internal object XTray {

    fun initialize(image: Image, onClick: (x: Int, y: Int) -> Unit) {
        createTrayIcon(image, onClick)
    }

    private fun createTrayIcon(
        image: Image,
        onClick: (x: Int, y: Int) -> Unit,
    ): TrayIcon {
        val systemTray = SystemTray.getSystemTray()

        val trayIcon = TrayIcon(image, "Open X")

        trayIcon.popupMenu = PopupMenu("X")

        trayIcon.addMouseListener(createTrayMouseListener { x, y ->
            onClick(x, y)
        })

        systemTray.add(trayIcon)

        return trayIcon
    }

    private fun createTrayMouseListener(
        onClick: (x: Int, y: Int) -> Unit,
    ): MouseAdapter {
        return object : MouseAdapter() {
            override fun mouseClicked(e: MouseEvent?) {
                e ?: return

                if (SwingUtilities.isLeftMouseButton(e)) {
                    println("${e.x} & ${e.y}")
                    onClick(calculateWindowX(e.x), calculateWindowY(e.y))
                }
            }
        }
    }

    private fun calculateWindowX(
        trayCenterX: Int,
        windowWidth: Int = 350,
        screenWidth: Int = Toolkit.getDefaultToolkit().screenSize.width,
    ): Int {
        val halfWindowWidth = windowWidth / 2

        return when {
            trayCenterX - halfWindowWidth < 0 -> 0 // Tray icon is closer to the left edge
            trayCenterX + halfWindowWidth > screenWidth -> screenWidth - windowWidth // Tray icon is closer to the right edge
            else -> trayCenterX - halfWindowWidth // Tray icon is in the middle of the screen
        }
    }

    private fun calculateWindowY(
        trayCenterY: Int,
        windowHeight: Int = 650,
        screenHeight: Int = Toolkit.getDefaultToolkit().screenSize.height,
    ): Int {
        val halfWindowHeight = windowHeight / 2

        return when {
            trayCenterY - halfWindowHeight < 0 -> 0 // Tray icon is closer to the top edge
            trayCenterY + halfWindowHeight > screenHeight -> screenHeight - windowHeight // Tray icon is closer to the bottom edge
            else -> trayCenterY - halfWindowHeight // Tray icon is in the middle of the screen
        }
    }
}

Other applications, like JetBrains Toolbox, show their window at startup, without any need to locate a icon by click on that icon. So, there's should be another solution.

I suspect that I need to use system APIs to accomplish this, but I'm wondering if there is a ready-made solution or library that provides this functionality. It seems like a common feature, and I don't want to reinvent the wheel.

I'd like to look at examples, but I can't find them.

1

There are 1 best solutions below

1
On

The system tray API in Java allows you to interact with a tray icon (add it, remove it, update its image or tooltip, etc.), but it does not provide a way to query its position on the screen. The mouse events associated with a tray icon provide coordinates relative to the icon itself, not the screen.

On Windows, you would need to interact with the Shell_TrayWnd and TrayNotifyWnd Windows controls that manage the system tray area and its icons. This would require calling Windows API functions and handling Windows messages, which cannot be done directly in Java, but would require using JNI or a third-party library like JNA (Java Native Access) or JNR (Java Native Runtime).