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
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:
| Packaging | Backend |
|---|---|
| MSIX | WinRT 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:
- User choices are final. Once the user disables auto-launch through system UI, the state is
DISABLED_BY_USERandenable()returnsBLOCKED_BY_USERuntil the user re-enables it. - Silent updates do not reset state. The Win32 backend writes to
Runonly onenable()and touchesStartupApprovedonly ondisable(). - MSIX requires the manifest extension. Without
<uap5:StartupTask>inPackage.appxmanifest, the API returnsUNSUPPORTED. SetaddAutoLaunchExtension = truein the plugin block and the plugin adds it.
API reference
AutoLaunch
AutoLaunch is an object; all methods are static.
| Method | Returns | Notes |
|---|---|---|
state() | AutoLaunchState | Current auto-launch state. |
enable() | AutoLaunchResult | Requests auto-launch on. |
disable() | AutoLaunchResult | Requests auto-launch off. |
openSystemSettings() | Boolean | Opens ms-settings:startupapps (Windows) or System Settings → General → Login Items (macOS). true if the UI launched. |
wasStartedAtLogin(args) | Boolean | Whether this process was spawned by login. Returns false in dev mode. |
isEnabled() | Boolean | true for ENABLED or ENABLED_BY_POLICY. |
isUserLocked() | Boolean | true when the state is DISABLED_BY_USER. |
diagnostic() | String | Human-readable backend trace for support tickets. |
preload() | Unit | Eagerly 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:
| Property | Affects |
|---|---|
taskId | MSIX startup task id; must match <uap5:StartupTask TaskId="...">. |
executablePath | Win32 HKCU\...\Run path. Resolved from the running process when null. |
autostartArgument | CLI marker appended to the registered command (default --nucleus-autostart). |
registryValueName | Win32 HKCU\...\Run value name. |
backgroundReason | Reason 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
keyAELaunchedAsLogInItemmarker from the AppleEvent thatloginwindowdispatches at login. Apple ships no public API for this. wasStartedAtLogin()returnsfalsein dev mode (ExecutableRuntime.isDev()) to avoid false positives from environment variables inherited from the parent shell. Production builds must set thenucleus.executable.typesystem property or ship the.nucleus-executable-typemarker file, orExecutableRuntimedefaults toDEVand the method always returnsfalse.
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.