Hello, I’m planning to build a small utility app t...
# compose-desktop
a
Hello, I’m planning to build a small utility app that’s going to live on the system tray and have a pop up UI that will appear when you click the tray icon. Much like the ToolBox app. I’m having difficulty figuring out how to show the window when the icon is clicked and make it disappear when clicked again. I looked up the sample project, but that only helps me to render a menu from the system tray. Is ToolBox opensource - I cannot find the source. Is there any other OpenSource projects that has a similar functionality that I can refer.?
a
Put a
Window
in your composition.
Have a mutable state
showWindow
that is controlled from your menu and in the composition do
Copy code
if (showWindow) Window(…) { }
a
Thank you. How would I toggle the
showWindow
when the tray icon is clicked. I was testing with
Tray(... ,onAction = { println("Tray.onAction" })
. As per the documentation:
Action performed when user clicks on the tray icon (double click on Windows, right click on macOs)
But I don’t the debug line printed when I click / double click / right click the tray icon.
a
a
I did try the examples from above link. In that example, the state
isOpen
is driven by the last item in the menu. I don’t want a menu when the icon is clicked. I want a window to appear just like the ToolBox.
onAction
does not get triggered when I click the tray Icon.
a
This seems to work for me:
Copy code
fun main() = application {
    var showWindow by remember { mutableStateOf(false) }
    val trayState = rememberTrayState()
    Tray(
        icon = MyAppIcon,
        state = trayState,
        onAction = { showWindow = !showWindow }
    )

    if (showWindow) {
        Window(onCloseRequest = { showWindow = false }) {}
    }
}

object MyAppIcon : Painter() {
    override val intrinsicSize = Size(256f, 256f)

    override fun DrawScope.onDraw() {
        drawRect(Color.Red)
    }
}
Right-clicking the tray icon (on macOS) opens and closes the window
If you want to react to a regular click, I’d recomment copy/pasting
ApplicationScope.Tray
into your app, and adding a click listener to it, like so:
Copy code
fun ApplicationScope.Tray(
    icon: Painter,
    state: TrayState = rememberTrayState(),
    tooltip: String? = null,
    onAction: () -> Unit = {},
    onClick: () -> Unit = {},
    menu: @Composable MenuScope.() -> Unit = {}
) {
    ...
    val currentOnAction by rememberUpdatedState(onAction)
    val currentOnClick by rememberUpdatedState(onClick)
    ...
    
    val tray = remember {
        TrayIcon(awtIcon).apply {
            isImageAutoSize = true

            addActionListener {
                currentOnAction()
            }
            addMouseListener(object: MouseAdapter() {
                override fun mouseClicked(e: MouseEvent) {
                    currentOnClick()
                }
            })
        }
    }
    ...
}
gratitude thank you 1
a
Thank you. The last snippet I exactly what I needed.
I also figured out the issue with my earlier code. I have the tap-to-click enabled on the macbook. For right-click, I tap with two fingers, which was not triggering the
onAction
. But when I actually press the trackpad with two fingers, it triggered the
onAction
. I don’t know why tapping with two fingers did not trigger the action, but pressing with two fingers did, while everywhere else I do two finger tap for right click. May be that’s not an issue with Compose and a java issue? I don’t know.
Thanks for your help.
m
This is quite difficult. The built-in
Tray
implementation doesn't consistently work cross-platform. For menus, they look really bad on Windows and Linux, and having a click action, you will find doesn't work on all OS either (I think it only works on Mac). The Toolbox app was my first thought as well – it seems to do it after all. It's not open source, but looking through the files you will find it uses it's own custom library for the tray icon, which turns out is available online: https://github.com/kropp/java-statusnotifier But it has no documentation and I wasn't able to get it to work. I don't know if the code on GitHub is outdated. The author (@kropp) did not respond when I asked about it in November, so I assume it's not intended to be used. I ended up using https://github.com/dorkbox/SystemTray It's not perfect, but it actually works in most cases on all 3 OSes.
a
Victor is now our team lead, so maybe he can shed some light :–)
The built-in
Tray
implementation doesn’t consistently work cross-platform. For menus, they look really bad on Windows and Linux, and having a click action, you will find doesn’t work on all OS either (I think it only works on Mac).
The click action as I described above doesn’t work on Windows?
k
Hey there, first of all, Marcin sorry that I missed the message, I haven't visited this Slack for years up until recently. Regarding the
java-statusnotifier
repo – it implements a very limited subset of tray icon features on Linux. There are known missing features there, and in Toolbox app we haven't had resources to implement/fix them all. On Windows and macOS we used platform APIs directly via interop. Again, there's not much to opensource there, as it was just a few lines of code calling system APIs and tailored for the specific use case. Overall, based on my Toolbox App experience, the tray/menubar icon behavior is very different on different OS (they even have different naming for that thingy), and on multitude implementations on Linux makes a true cross-platform solution a very hard task.
m
I checked it again on all OS. When using `Tray`: On Linux (KDE): - Icon doesn't display properly - Icon tooltip shows an unchangeable "JavaEmbeddedFrame" text - Left click fires
onAction
correctly - Right click opens a menu, but the menu items cannot have icons, and the menu looks terrible. The menu appears on a different monitor than the icon. On Windows 11: - Icon displays at a lower resolution - Icon tooltip shows correct text - Left click does nothing - Double click fires
onAction
- Right click opens a menu, but the menu items cannot have icons, and the menu looks outdated (but not as bad as on Linux) On macOS: - Icon displays at a lower resolution - No tooltips - Left click open a menu, but the menu items cannot have icons - Right click fires
onAction
correctly When using the SystemTray library, there is a different set of tradeoffs, but overall it's much better: On Linux (KDE): - Icon displays correctly - Icon tooltip shows correct text - Both left click and right click open the menu. No action possible. - The menu can have icons and styling On macOS: - Icon displays correctly - No tooltips - Left click opens a menu, menu items can have icons - Right click throws an exception (catch to do nothing) On Windows 11: - Icon displays in full resolution, but is a little blurry instead. - Icon tooltip shows correct text - Both left click and right click open the menu. No action possible. - The menu can have icons and styling
Linux
Tray
Windows
Tray
Linux and Windows
SystemTray
Even if the menu functionality was completely missing, it would great to have the same functionality as Toolbox has in Compose Desktop. Correct looking icon that fires an action on all OS.
a
I don’t know about the icon (it probably breaks somewhere in the transition from Compose to AWT images), but I’m guessing that adding a mouse listener as above should give you a click action.
r
I needed to implement this in my codebase recently, and ended up realising that it was easier to just make my own menu inside a window for linux/windows. macOS is fairly well supported by dorkbox.SystemTray, but Windows definitely left a lot to be desired. hidpi scaling issue, ugly menu rendering etc...
Fairly straightforward with a modified
Tray()
composable that responds to mouse events: https://gist.github.com/iamcalledrob/9f3b35955e6650365314703fee3c781a
With custom menu implemented in compose (could be anything though, it's just a window)
m
@rob42 Did you try it on Linux? I have a feeling it won't work.
r
I haven't yet, but this is just opening a window at x/y coords, so it shouldn't require anything special?
m
Your window has rounded corners and transperency doesn't work on Linux, unless you make your menu a rectangle
r
Interesting -- so
Window(transparent = true) {}
is non-functional on linux?
m
It's mentioned at the end here: https://github.com/JetBrains/compose-multiplatform/blob/master/tutorials/Window_API_new/README.md If it works, then it only works on systems with 1 monitor.
So in practice yes.
r
Ouch. Well luckily, these menus never move between displays. Looks like this has been fixed in JDK 22 though (not that I'm using that yet)
The fix looks relatively simple, I wonder if there's a way to hack it as a workaround for older JDKs?
m
Oh interesting, I will try a JDK 22 to see if it's fixed.
r
If it is, then theoretically, this should work for older JDKs?
Copy code
val gc = window.peer.getAppropriateGraphicsConfiguration(window.graphicsConfiguration);
window.graphicsConfiguration = gc
m
> Well luckily, these menus never move between displays. It's not that simple. If you ever move any of your window in the lifetime of your app, no windows will ever work again (until a restart).
😢 1
r
Oh interesting, I will try a JDK 22 to see if it's fixed.
Would love to know too if you end up testing this
m
On JDK 17, transparency doesn't work at all for me, even if I don't move any windows. On JDK 22 this indeed seems to be fixed, I can have a transparent window and moving it between displays doesn't break anything. But... in both cases if I create a transparent window (whether it works or not) I am getting
Copy code
[SKIKO] warn: Failed to create Skia OpenGL context!
java.lang.RuntimeException: Can't wrap nullptr
[SKIKO] warn: Exception in draw scope
org.jetbrains.skiko.RenderException: Cannot init graphic context
and Compose switches to software rendering, making everything very slow.
r
Well at least that's an improvement...!
m
Yep, works now, but still breaks hardware acceleration.
💯 1
r
I wonder if this is now an issue in Compose/Skiko, rather than the JDK?
As an aside, I see great performance with software rendering on Windows, is that not the case elsewhere?
m
My app has a complex Canvas with multiple animations running. Software rendering makes it a slideshow.
For apps that don't do any such thing, it's probably fine.
r
Thank you! Starred