Nucleus
Lifecycle

Launch at login

Cross-platform start-at-login — MSIX startup tasks, Win32 registry, macOS SMAppService, Linux systemd, Flatpak portal.

Launch-at-login should be one boolean. It isn't — every OS has its own mechanism, every packaging format reroutes it differently, and "the user disabled this via Task Manager" is a state you must respect. autolaunch exposes a single API that resolves the right backend for the current runtime and refuses to disrespect explicit user choices.

TL;DR

  • One API across MSIX, Win32 (MSI/NSIS), macOS DMG/PKG (macOS 13+), Linux systemd-user, Linux Flatpak portal.
  • Backend selection is automatic — based on ExecutableRuntime.type().
  • Five states: ENABLED, DISABLED, DISABLED_BY_USER, DISABLED_BY_POLICY, ENABLED_BY_POLICY, UNSUPPORTED.
  • Never loop on BLOCKED_BY_USER — that's an explicit user choice and the API will keep refusing.
  • wasStartedAtLogin(args) tells you if this very launch was a login one — useful to skip splash, start minimized, etc.

Install

dependencies {
    implementation("dev.nucleusframework:nucleus.autolaunch:<version>")
}

On macOS, the JNI bridge in nucleus.service-management-macos is pulled in transitively — no extra declaration.

Quickstart

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 env */ }
    else -> {}
}

// At main() entry — detect a login-triggered launch
if (AutoLaunch.wasStartedAtLogin(args)) {
    startMinimized = true
}

In Compose:

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

How it works

PackagingBackend
MSIXWinRT Windows.ApplicationModel.StartupTask. Manifest extension auto-injected by the plugin via appx { addAutoLaunchExtension = true }.
Win32 (MSI / NSIS)HKCU\Software\Microsoft\Windows\CurrentVersion\Run + reading StartupApproved\Run to detect user disable.
macOS DMG / PKG (13+)SMAppService.mainApp. Appears under System Settings → General → Login Items.
Linux (deb / rpm / AppImage / dev)systemd user service.
Linux (Flatpak)XDG Desktop Portal org.freedesktop.portal.Background.RequestBackground.

The runtime picks the backend at startup using ExecutableRuntime. None of this requires per-OS conditional code on your side.

Three invariants the module enforces and you should not work around:

  1. Never loop on BLOCKED_BY_USER. When the user toggled the app off via Task Manager / System Settings, calling enable() again is a no-op on MSIX and would be disrespectful on Win32. The API returns BLOCKED_BY_USER instead.
  2. Silent updates must not reset auto-launch state. The Win32 backend only writes to Run when you call enable(). It never touches StartupApproved unless you call disable().
  3. MSIX requires the manifest extension. Without <uap5:StartupTask> in Package.appxmanifest, the API returns UNSUPPORTED. Set addAutoLaunchExtension = true in your plugin block and the plugin handles it.

Reference

AutoLaunch

MethodReturnsNotes
state()AutoLaunchStateCurrent state.
enable()AutoLaunchResultSee result table.
disable()AutoLaunchResult
openSystemSettings()Booleanms-settings:startupapps (Windows), Login Items pane (macOS), best-effort on Linux.
wasStartedAtLogin(args)BooleanDetects whether the current process was spawned by login. Returns false in dev mode.
diagnostic()StringHuman-readable backend trace — for support tickets.
preload()UnitEagerly initializes the backend (optional — saves a few ms on first call).

AutoLaunchState

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

AutoLaunchResult

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

AutoLaunchConfig

Override defaults before the first AutoLaunch call:

PropertyAffects
taskIdMSIX startup task id.
executablePathWin32, Linux systemd path; Linux Flatpak basename.
autostartArgumentCLI marker added to the registered command (Win32 + Linux Flatpak).
registryValueNameWin32 HKCU\...\Run value name.
backgroundReasonReason string for the Flatpak portal prompt.

Notes

  • MSIX-only quirk: toggling auto-launch programmatically does not update the Task Manager "Startup apps" tab live — Windows refreshes that view after the next login. The state itself is correct; only the Task Manager display lags.
  • Detection of a login-triggered launch on macOS relies on the undocumented LaunchInstanceID env var that launchd sets on managed jobs — Apple ships no public API for this (radar FB10207829). It's empirical but reliable.
  • wasStartedAtLogin() short-circuits to false in dev mode (ExecutableRuntime.isJar()) to avoid false positives from env vars inherited from the parent shell.