Desktop

Tray icon, close to tray and autostart

Add a tray icon and menu, hide the window on close, start with the computer and allow only one copy per user.

Available since 0.3.0. A mail client or chat app usually lives in the tray, hides instead of quitting when its window is closed, and can start with the computer. These are separate pieces, so you choose which to use, and each can be changed while the app runs.

Tray icon and menu

Application app = ...;
WebWindow main = ...;

if (app.isTraySupported()) {                                   // Windows: always. Linux: needs a tray host
    Tray tray = app.createTray(iconBytes, "Parsel");           // call on the UI thread
    tray.onClick(main::focus);                                 // primary click: bring the window back
    tray.setMenu(TrayMenu.of(
            TrayMenu.action("Open Parsel", main::focus),
            TrayMenu.separator(),
            TrayMenu.check("Close to tray", closeToTray, on -> setCloseToTray(on)),
            TrayMenu.check("Start with the computer", autostart, on -> setAutostart(on)),
            TrayMenu.separator(),
            TrayMenu.action("Quit", app::quit)));
}
  • Menus are immutable. To tick a box or change a label, build the menu again and call tray.setMenu(...). tray.setIcon and tray.setTooltip work the same way, for example for "3 unread". Menu callbacks run on the UI thread.
  • Windows: the tray menu is a native menu, drawn in dark mode when the system is dark. It supports checks, disabled entries, separators and submenus. If Explorer restarts, the icon is put back.
  • Linux: the tray is a StatusNotifierItem with a dbusmenu menu, which KDE Plasma, XFCE, Cinnamon, MATE, Budgie and others show. Stock GNOME needs the AppIndicator extension, otherwise isTraySupported() is false.

Close to tray and autostart

main.setCloseBehavior(closeToTray ? CloseBehavior.HIDE : CloseBehavior.CLOSE);    // takes effect on the next click of the close button

Autostart autostart = app.autostart();
autostart.enable(List.of(exePath, "--minimized"));     // start with the computer, minimized...
autostart.enable(List.of(exePath));                    // ...or not: calling it again replaces the entry
autostart.disable(); autostart.isEnabled();
  • Start minimized is an argument you choose: put --minimized in the autostart command, and in main create the window with withVisible(false) (and a tray) when args contains it. Only hide at startup if app.isTraySupported().
  • Close to tray needs a tray. CloseBehavior.HIDE only hides the window; focus() or show() brings it back, while close() and app.quit() still really close it.
  • The command: give the launcher of your installed or packaged app (on Windows a quoted path in the Run key, on Linux the Exec= line of ~/.config/autostart/<appId>.desktop). Autostart.currentCommand(extraArgs) describes the running process, which is fine for java -jar while developing.

One copy only

A tray app that starts at sign-in should not start twice.

public static void main(String[] args) {
    AppOptions options = AppOptions.of("com.example.parsel");
    SingleInstance instance = SingleInstance.acquire(options, List.of(args), otherArgs -> app.dispatch(() -> main.focus()));
    if (!instance.isPrimary()) {
        return;                       // the running copy was told (and receives this copy's arguments)
    }
    ...
}

SingleInstance takes a lock file in the data directory (released by the system if the app dies) and talks to the running copy over a Unix domain socket that only the same user can reach. The callback runs on a thread of its own, so use app.dispatch.