5

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.

  • I know how to set position, I don't know WHERE I should set it as there's no API (at least I cannot find) to locate system tray icon position (x & y). I shown example in my question. – Vadym Yaroshchuk Jun 18 '23 at 13:16
  • You put the icon in the system tray and Windows calculates the position of the icon in the system tray. Here's [my system tray](https://i.imgur.com/yDQNb0j.png) as an example. – Gilbert Le Blanc Jun 18 '23 at 13:23
  • yes and how does it solve my question? I still cannot put my window under / above (depending on the system) this tray icon. Because I need to somehow get the position of this icon. – Vadym Yaroshchuk Jun 18 '23 at 13:29
  • my application is helper app that should appear only if user clicked on tray icon (and disappear in the same way). And this app should appear at this icon, not somewhere else. – Vadym Yaroshchuk Jun 18 '23 at 13:31
  • I supplemented question with video where I showed how it looks right now. I want same behaviour (without problems I mention ofc), but using normal location of the icon. – Vadym Yaroshchuk Jun 18 '23 at 13:41
  • I'm trying to figure this out too, I wish Jetbrains would make a demo on how they made their Toolbox app with compose. It would really help the community and the project docs. Also, did you manage to hide the application icon in the dock? Did you do so from the info.plist or in another way? – Giuliopime Jul 08 '23 at 08:00
  • @Giuliopime, for now, I just hide it programically (just using visible property in compose window) – Vadym Yaroshchuk Jul 12 '23 at 22:44
  • Btw you coudl simply save the first opened position and then open it always in that position, would work too well with multiple monitors tho – Giuliopime Jul 13 '23 at 11:37
  • 1
    @Giuliopime if user will change his system bar position (for example, from top to bottom) we will have invalid behaviour. For now, I found solution for Windows, but still in search about Linux (it's the most hard part as there's a lot of system tray providers) and MacOS (can't even find system API for it). Leave it for those who needs that – https://github.com/timemates/app/blob/dev/foundation/system-tray/src/main/kotlin/io/timemates/desktop/systray/WindowsTrayIconPositionProvider.kt#L9 (I haven't tested as I work on MacOS, but seems to be working well). – Vadym Yaroshchuk Jul 17 '23 at 21:17
  • Note that the toolbox app doesn't really work that well on Ubuntu. It uses a context menu on the icon to open the window, instead of opening on click like on Mac/Windows. And it's not undecorated but has the ugly default window bar. – Jorn Jul 28 '23 at 07:35
  • sure, I would like to know at least about how can I implement it on Windows / MacOS (this platforms have much more priority for me) – Vadym Yaroshchuk Jul 28 '23 at 11:35

1 Answers1

0

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).

Jason091
  • 11
  • 1
  • 3