Nucleus
Lifecycle

Launcher (macOS)

Populate the Dock right-click menu of a macOS app with items, submenus, and separators, and handle clicks from Kotlin.

launcher-macos lets you populate the Dock right-click menu of a macOS app from Kotlin. You define the menu entries — including submenus, separators, and disabled items — and receive a callback on the Swing EDT when the user clicks one.

Add the dependency

build.gradle.kts
dependencies {
    implementation("dev.nucleusframework:nucleus.launcher-macos:2.0.7")
}

Populate the Dock menu

Call MacOsDockMenu.setDockMenu(...) with the items to display, then set a listener to handle clicks. Each item carries a numeric id that the listener receives.

import dev.nucleusframework.launcher.macos.DockMenuItem
import dev.nucleusframework.launcher.macos.DockMenuListener
import dev.nucleusframework.launcher.macos.MacOsDockMenu

MacOsDockMenu.setDockMenu(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()
    }
}

Call MacOsDockMenu.clearDockMenu() to remove the menu and restore the default Dock behavior.

All methods are no-ops off macOS and when the native library fails to load. Check MacOsDockMenu.isAvailable before you build the menu.

How it works

The first call to setDockMenu(...) installs a method swizzle on the running NSApplicationDelegate to intercept applicationDockMenu:. From then on, AppKit asks Nucleus for the menu whenever the user right-clicks (or Ctrl-clicks) the Dock tile.

Click callbacks are delivered on the Swing EDT, so you can mutate Compose state directly from the listener without dispatching to another thread.

macOS strips images from Dock menu items. Only text, separators, submenus, and the enabled/disabled state are rendered — the Dock process owns the drawing, not your app.

For dragging files onto the Dock icon, AWT already exposes Desktop.setOpenFileHandler (and Desktop.setOpenURIHandler for deep links). launcher-macos does not duplicate that.

API reference

MacOsDockMenu

The entry point. A single object with one global listener.

MemberNotes
isAvailable: Booleanfalse off macOS or when the native library failed to load.
listener: DockMenuListener?One global listener; dispatch by id inside it.
setDockMenu(items: List<DockMenuItem>)Replaces the menu. Installs the swizzle on the first call.
clearDockMenu()Removes the menu; the OS default returns.

DockMenuItem

A data class describing one entry.

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

DockMenuItem.separator(id) builds a separator entry.

DockMenuListener

A fun interface with a single method, onItemClicked(itemId: Int), invoked on the Swing EDT.

Notes

  • Test from a packaged build or runDistributable. The java launcher icon shown by ./gradlew run uses no bundle identifier and will not display the menu meaningfully.
  • ProGuard / GraalVM: keep dev.nucleusframework.launcher.macos.NativeMacOsDockMenuBridge reachable. The module ships the metadata.

What's next