Nucleus
Lifecycle

Launcher (macOS)

Drive the Dock right-click menu of your macOS app — items, submenus, separators, click callbacks.

The macOS Dock icon has a right-click menu nobody else can populate for you — and most apps just leave it on "Quit / Options / Show All Windows". launcher-macos lets you add structured menu entries, submenus, and disabled items, with click callbacks back on the EDT.

TL;DR

  • One singleton, MacOsDockMenu. setItems(...) replaces the menu, clear() removes it.
  • Backed by Objective-C method swizzling of applicationDockMenu: — works on stock OpenJDK, no JBR required.
  • Items have id, title, optional enabled and nested children for submenus. DockMenuItem.separator(id) for visual breaks.
  • Click callbacks fire on the Swing EDT — safe to update Compose state directly.

Install

dependencies {
    implementation("dev.nucleusframework:nucleus.launcher-macos:<version>")
}

Quickstart

import dev.nucleusframework.launcher.macos.*

MacOsDockMenu.setItems(listOf(
    DockMenuItem(id = 1, title = "New Window"),
    DockMenuItem(id = 2, title = "Open File..."),
    DockMenuItem.separator(id = 3),
    DockMenuItem(
        id = 4, title = "Recent",
        children = listOf(
            DockMenuItem(id = 41, title = "project.kt"),
            DockMenuItem(id = 42, title = "build.gradle.kts"),
        ),
    ),
    DockMenuItem(id = 5, title = "Preferences"),
))

MacOsDockMenu.listener = DockMenuListener { id ->
    when (id) {
        1 -> openNewWindow()
        2 -> openFile()
        in 40..49 -> openRecent(id)
        5 -> showPreferences()
    }
}

How it works

The native bridge swizzles applicationDockMenu: on the running NSApplicationDelegate the first time you call setItems(...). From then on, AppKit asks Nucleus for the menu whenever the user right-clicks the Dock tile. Menu construction runs on the main thread via dispatch_sync; click callbacks are bounced back to the Swing EDT via SwingUtilities.invokeLater, so you can directly mutate Compose state.

macOS strips images from Dock menu items — only text, separators, submenus, and enabled/disabled state survive. The Dock process owns the rendering, not your app.

This is complementary to taskbar-progress, which handles the dock badge and bounce. Use both side by side.

For drag-files-onto-the-dock-icon, AWT already exposes Desktop.setOpenFileHandler (and for deep links, Desktop.setOpenURIHandler — see Deep links). launcher-macos does not duplicate that.

Reference

MacOsDockMenu

MemberNotes
isAvailable: Booleanfalse outside macOS or when the native lib failed to load.
listener: DockMenuListener?One global listener — dispatch by id inside.
setItems(items: List<DockMenuItem>)Replaces the menu. Installs the swizzle on first call.
clear()Drops the menu (the OS default returns).

DockMenuItem

PropertyDefault
id: Intrequired, > 0Identifier passed to the listener.
title: String""Display text.
enabled: BooleantrueWhether the item is clickable.
children: List<DockMenuItem>emptyList()Non-empty creates a submenu.

DockMenuItem.separator(id) is the idiomatic way to insert separators.

Notes

  • Test from a packaged build (or runDistributable). The java launcher icon shown by ./gradlew run doesn't have your bundle id and won't show the menu meaningfully.
  • ProGuard / GraalVM: keep dev.nucleusframework.launcher.macos.NativeMacOsDockMenuBridge reachable. The module ships the metadata.