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
| Packaging | Backend |
|---|---|
| MSIX | WinRT 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:
- Never loop on
BLOCKED_BY_USER. When the user toggled the app off via Task Manager / System Settings, callingenable()again is a no-op on MSIX and would be disrespectful on Win32. The API returnsBLOCKED_BY_USERinstead. - Silent updates must not reset auto-launch state. The Win32 backend only writes to
Runwhen you callenable(). It never touchesStartupApprovedunless you calldisable(). - MSIX requires the manifest extension. Without
<uap5:StartupTask>inPackage.appxmanifest, the API returnsUNSUPPORTED. SetaddAutoLaunchExtension = truein your plugin block and the plugin handles it.
Reference
AutoLaunch
| Method | Returns | Notes |
|---|---|---|
state() | AutoLaunchState | Current state. |
enable() | AutoLaunchResult | See result table. |
disable() | AutoLaunchResult | |
openSystemSettings() | Boolean | ms-settings:startupapps (Windows), Login Items pane (macOS), best-effort on Linux. |
wasStartedAtLogin(args) | Boolean | Detects whether the current process was spawned by login. Returns false in dev mode. |
diagnostic() | String | Human-readable backend trace — for support tickets. |
preload() | Unit | Eagerly 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:
| Property | Affects |
|---|---|
taskId | MSIX startup task id. |
executablePath | Win32, Linux systemd path; Linux Flatpak basename. |
autostartArgument | CLI marker added to the registered command (Win32 + Linux Flatpak). |
registryValueName | Win32 HKCU\...\Run value name. |
backgroundReason | Reason 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
LaunchInstanceIDenv var thatlaunchdsets on managed jobs — Apple ships no public API for this (radar FB10207829). It's empirical but reliable. wasStartedAtLogin()short-circuits tofalsein dev mode (ExecutableRuntime.isJar()) to avoid false positives from env vars inherited from the parent shell.