Nucleus
Lifecycle

Launch at login

Enable, disable, and query start-at-login from Kotlin with a single API that resolves the backend for the current packaging format.

The autolaunch module lets you enable, disable, and query start-at-login from Kotlin without writing per-OS code. It resolves the backend for the current packaging format at runtime — MSIX startup tasks, the Win32 registry, macOS SMAppService, Linux systemd user services, and the Flatpak background portal — and respects states the user set outside your app.

Add the dependency

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

On macOS, the JNI bridge in nucleus.service-management-macos is pulled in transitively, so you do not declare it separately.

Check and toggle auto-launch

Read the current state with AutoLaunch.state() and branch on it. Call enable() or disable() to change it, and openSystemSettings() when the user must act in system UI:

import dev.nucleusframework.autolaunch.AutoLaunch
import dev.nucleusframework.autolaunch.AutoLaunchState

when (AutoLaunch.state()) {
    AutoLaunchState.ENABLED          -> {}
    AutoLaunchState.DISABLED         -> AutoLaunch.enable()
    AutoLaunchState.DISABLED_BY_USER -> AutoLaunch.openSystemSettings()
    AutoLaunchState.UNSUPPORTED      -> { /* macOS < 13, unsupported Linux environment */ }
    else -> {}
}

Never loop on BLOCKED_BY_USER. When the user disables the app through Task Manager or System Settings, enable() returns BLOCKED_BY_USER and keeps refusing until the user re-enables it manually. Send them to openSystemSettings() instead.

To wire a toggle in Compose, drive it from state():

Switch(
    checked = AutoLaunch.state() == AutoLaunchState.ENABLED,
    enabled = AutoLaunch.state() != AutoLaunchState.DISABLED_BY_USER,
    onCheckedChange = { on ->
        if (on) AutoLaunch.enable() else AutoLaunch.disable()
    },
)

Detect a login launch

wasStartedAtLogin(args) reports whether the current process was spawned by the login mechanism rather than started by hand. Check it at your main() entry point to skip a splash screen or start minimized:

fun main(args: Array<String>) {
    if (AutoLaunch.wasStartedAtLogin(args)) {
        startMinimized = true
    }
    // ...
}

On macOS the login AppleEvent is delivered after NSApplication.run() starts, so the first call from main() usually returns false. The method caches positive results, so it is safe to poll it from a LaunchedEffect once the Compose loop is up.

How it works

The runtime selects a backend from the operating system and the detected executable type — no per-OS conditional code on your side:

PackagingBackend
MSIXWinRT Windows.ApplicationModel.StartupTask. The plugin injects the manifest extension when appx { addAutoLaunchExtension = true }.
Win32 (MSI / NSIS)HKCU\Software\Microsoft\Windows\CurrentVersion\Run, reading StartupApproved\Run to detect a user disable.
macOS DMG / PKG (13+)SMAppService.mainApp. Appears under System Settings → General → Login Items.
Linux (host: deb / rpm / AppImage)systemd user service under ~/.config/systemd/user/.
Linux (Flatpak)XDG Desktop Portal org.freedesktop.portal.Background.

Three invariants the module enforces, which you should not work around:

  1. User choices are final. Once the user disables auto-launch through system UI, the state is DISABLED_BY_USER and enable() returns BLOCKED_BY_USER until the user re-enables it.
  2. Silent updates do not reset state. The Win32 backend writes to Run only on enable() and touches StartupApproved only on disable().
  3. MSIX requires the manifest extension. Without <uap5:StartupTask> in Package.appxmanifest, the API returns UNSUPPORTED. Set addAutoLaunchExtension = true in the plugin block and the plugin adds it.

API reference

AutoLaunch

AutoLaunch is an object; all methods are static.

MethodReturnsNotes
state()AutoLaunchStateCurrent auto-launch state.
enable()AutoLaunchResultRequests auto-launch on.
disable()AutoLaunchResultRequests auto-launch off.
openSystemSettings()BooleanOpens ms-settings:startupapps (Windows) or System Settings → General → Login Items (macOS). true if the UI launched.
wasStartedAtLogin(args)BooleanWhether this process was spawned by login. Returns false in dev mode.
isEnabled()Booleantrue for ENABLED or ENABLED_BY_POLICY.
isUserLocked()Booleantrue when the state is DISABLED_BY_USER.
diagnostic()StringHuman-readable backend trace for support tickets.
preload()UnitEagerly resolves the backend and loads its JNI library. Call from a background thread to avoid a first-call stall on macOS.

AutoLaunchState

ENABLED, DISABLED, DISABLED_BY_USER, DISABLED_BY_POLICY, ENABLED_BY_POLICY, UNSUPPORTED.

DISABLED_BY_POLICY and ENABLED_BY_POLICY are read-only Group Policy states (MSIX only).

AutoLaunchResult

OK, UNCHANGED, BLOCKED_BY_USER, BLOCKED_BY_POLICY, UNSUPPORTED, ERROR.

AutoLaunchConfig

Override the defaults before the first AutoLaunch call:

PropertyAffects
taskIdMSIX startup task id; must match <uap5:StartupTask TaskId="...">.
executablePathWin32 HKCU\...\Run path. Resolved from the running process when null.
autostartArgumentCLI marker appended to the registered command (default --nucleus-autostart).
registryValueNameWin32 HKCU\...\Run value name.
backgroundReasonReason string shown in the Flatpak portal prompt.

Notes

  • On MSIX, toggling auto-launch programmatically does not refresh the Task Manager "Startup apps" tab live — Windows updates that view after the next login. The stored state is correct; only the display lags.
  • macOS login detection reads the keyAELaunchedAsLogInItem marker from the AppleEvent that loginwindow dispatches at login. Apple ships no public API for this.
  • wasStartedAtLogin() returns false in dev mode (ExecutableRuntime.isDev()) to avoid false positives from environment variables inherited from the parent shell. Production builds must set the nucleus.executable.type system property or ship the .nucleus-executable-type marker file, or ExecutableRuntime defaults to DEV and the method always returns false.

What's next

  • Executable type — how the runtime detects the packaging format that drives backend selection.
  • Service management — register login items, agents, and daemons on macOS.
  • Single instance — route login launches into an already-running process.
  • Scheduler — run background work after a login launch.