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, optionalenabledand nestedchildrenfor 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
| Member | Notes |
|---|---|
isAvailable: Boolean | false 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
| Property | Default | |
|---|---|---|
id: Int | required, > 0 | Identifier passed to the listener. |
title: String | "" | Display text. |
enabled: Boolean | true | Whether 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). Thejavalauncher icon shown by./gradlew rundoesn't have your bundle id and won't show the menu meaningfully. - ProGuard / GraalVM: keep
dev.nucleusframework.launcher.macos.NativeMacOsDockMenuBridgereachable. The module ships the metadata.