Nucleus
Lifecycle

Launcher (Linux)

Set count badges, progress bars, urgency flags, and right-click quicklist menus on the Linux launcher from Kotlin.

launcher-linux maps the com.canonical.Unity.LauncherEntry D-Bus interface and the com.canonical.dbusmenu protocol to Kotlin. Use it to draw count badges, progress bars, and urgency flags on your launcher icon, and to attach a right-click quicklist menu. Desktop shells that implement the interface include Unity, KDE Plasma, Plank, and budgie-panel.

Add the dependency

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

Set badges and progress

Build the launcher URI from your .desktop file id, then set individual properties or push them all in one signal:

import dev.nucleusframework.launcher.linux.LauncherProperties
import dev.nucleusframework.launcher.linux.LinuxLauncherEntry

val appUri = LinuxLauncherEntry.appUri("myapp.desktop")

// Badge count
LinuxLauncherEntry.setCount(appUri, 42)

// Progress bar (0.0..1.0)
LinuxLauncherEntry.setProgress(appUri, 0.65)

// Set several properties in one signal
LinuxLauncherEntry.update(
    appUri,
    LauncherProperties(
        count = 5,
        countVisible = true,
        progress = 0.8,
        progressVisible = true,
        urgent = false,
    ),
)

setCount and setProgress are wrappers over update, so a single call that sets several properties emits one D-Bus signal.

Add a quicklist menu

A quicklist is the right-click menu on the launcher icon. Create a LinuxQuicklist on a D-Bus object path, set a click listener, install the menu, then point the launcher entry at the quicklist path:

import dev.nucleusframework.freedesktop.icons.FreedesktopIcon
import dev.nucleusframework.launcher.linux.DbusmenuItem
import dev.nucleusframework.launcher.linux.LauncherProperties
import dev.nucleusframework.launcher.linux.LinuxLauncherEntry
import dev.nucleusframework.launcher.linux.LinuxQuicklist

val quicklist = LinuxQuicklist("/com/example/MyApp/Menu")

quicklist.listener = LinuxQuicklist.Listener { id ->
    when (id) {
        1 -> openNewWindow()
        2 -> openFile()
        8 -> exitApp()
    }
}

quicklist.setMenu(
    listOf(
        DbusmenuItem(id = 1, label = "New Window", icon = FreedesktopIcon.Action.WINDOW_NEW),
        DbusmenuItem(id = 2, label = "Open File", icon = FreedesktopIcon.Action.DOCUMENT_OPEN),
        DbusmenuItem.separator(id = 3),
        DbusmenuItem(
            id = 8,
            label = "Quit",
            icon = FreedesktopIcon.Action.APPLICATION_EXIT,
            disposition = DbusmenuItem.Disposition.ALERT,
        ),
    ),
)

LinuxLauncherEntry.update(appUri, LauncherProperties(quicklist = quicklist.objectPath))

Icons use freedesktop-icons for symbolic names.

Clear the quicklist and release the D-Bus server on shutdown:

LinuxLauncherEntry.update(appUri, LauncherProperties(quicklist = ""))
quicklist.dispose()

How it works

The Unity Launcher API is a D-Bus signal protocol. You emit an Update signal on com.canonical.Unity.LauncherEntry carrying your .desktop file URI and a dictionary of properties (count, progress, urgent, quicklist path). Desktop shells subscribe to the signal and render the changes on your launcher icon. Nucleus talks to D-Bus through GIO/GDBus over a single native bridge, without a Java D-Bus library.

Each LinuxQuicklist registers its own com.canonical.dbusmenu object. The shell queries that object for the layout, and item clicks are dispatched back on the Swing event dispatch thread with SwingUtilities.invokeLater.

The .desktop filename determines which launcher icon receives the signals. The name derived from NucleusApp.appId is usually correct; pass a different id to LinuxLauncherEntry.appUri("custom.desktop") when your packaging uses another name.

API reference

Update the launcher entry

LinuxLauncherEntry is an object.

MemberDescription
isAvailable: Booleanfalse outside Linux or when the native library failed to load.
appUri(desktopFileId: String): StringBuilds the application://<id> URI used as the signal subject.
update(appUri, properties: LauncherProperties): BooleanEmits one Update signal with every non-null field.
setCount / clearCount / setProgress / clearProgress / setUrgent / setUpdatingWrappers over update.
registerQueryHandler(appUri) / unregister()Register a Query handler for the entry state, then release it on shutdown.

Describe properties

LauncherProperties has count, countVisible, progress, progressVisible, urgent, quicklist (the object path of the menu server, or an empty string to unset it), and updating. All fields are nullable, and null fields are skipped when the signal is emitted.

Build a quicklist

MemberDescription
objectPath: StringThe D-Bus path you bound. Pass it to LauncherProperties(quicklist = ...).
listener: Listener?Click callback; Listener is a fun interface with onItemClicked(itemId: Int).
setMenu(items: List<DbusmenuItem>): BooleanReplaces the menu layout and registers the D-Bus object.
dispose()Tears down the D-Bus server.

Build a menu item

DbusmenuItem has id, label (_ marks a mnemonic), icon (a FreedesktopIcon), enabled, visible, type (ItemType.STANDARD or ItemType.SEPARATOR), toggleType (ToggleType.NONE, CHECKBOX, or RADIO), toggleState (-1 indeterminate, 0 unchecked, 1 checked), shortcut, disposition (Disposition.NORMAL, INFORMATIONAL, WARNING, or ALERT), and children.

DbusmenuItem.separator(id) builds a separator.

Notes

  • The dbusmenu protocol is stateless on the server side; the shell re-queries the layout on every open. To change a toggle's check state, call setMenu(...) again with the new toggleState.
  • For cross-platform progress bars, see taskbar-progress, which delegates to launcher-linux on Linux.
  • XFCE without the docklike-plugin doesn't implement the Unity API. Detection succeeds, but nothing is rendered.

What's next