feat(autolaunch): cross-platform auto-launch at user login#205
Merged
kdroidFilter merged 8 commits intomainfrom Apr 18, 2026
Merged
feat(autolaunch): cross-platform auto-launch at user login#205kdroidFilter merged 8 commits intomainfrom
kdroidFilter merged 8 commits intomainfrom
Conversation
New autolaunch module with unified API across MSIX and Win32 packaging. MSIX uses WinRT StartupTask; Win32 uses HKCU\Run + StartupApproved parity rule. Detects auto-launched starts via CLI marker (Win32) and parent-process walk to sihost.exe (MSIX). Plugin auto-injects StartupTask TaskId into app metadata when addAutoLaunchExtension is enabled. Example gains an Auto-Launch tab and home banner when started at login.
Wires auto-launch at login for macOS (DMG and PKG alike) on top of the existing service-management-macos bridge. The entry appears under System Settings → Login Items → Open at Login, matching the behavior of sindresorhus/LaunchAtLogin-Modern. Runtime detection uses the official kAEOpenApplication AppleEvent with the keyAELaunchedAsLogInItem marker: the native bridge installs an observer at dylib-load time (__attribute__((constructor))) so the flag is captured before AWT's NSApplication consumes the event. The AutoLaunchBackend SPI gains a default wasStartedAtLogin(args) impl based on the CLI marker, overridden by MSIX and macOS backends. The dispatcher loads the macOS backend reflectively so consumers who don't ship PKG/DMG on macOS don't need to add the service-management-macos dependency. Requires macOS 13+ (SMAppService); older releases return UNSUPPORTED.
AutoLaunch.preload() is the standard JVM-style warmup hook that resolves the platform backend (and loads any JNI it requires) on the calling thread, so apps can run it from a background daemon thread early in main() to avoid first-touch latency on the EDT. Also restructures the example to read startedAtLogin lazily from inside NucleusContent rather than from main(): on macOS the kAEOpenApplication AppleEvent is only delivered once NSApp.run() starts, so the early call from main() always returns false there. Stashing the args and re-querying from a Composable is the JVM equivalent of checking the flag in Cocoa's applicationDidFinishLaunching delegate.
Use SMAppService directly instead of custom JNI wrapper.
…chInstanceID Empirical signal: launchd injects LaunchInstanceID env var only for managed jobs (SMAppService.mainApp login items), not for user-initiated launches (Finder/Dock). Verified by comparing boot-time vs manual launch diagnostics on macOS 14.7. Also adds MacLaunchDiagnostic utility to capture process bootstrap context (launchctl print, ps tree, environment, sysctl) for debugging and analysis. Example app displays diagnostic in Auto-Launch screen for inspection. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…atpak portal Implements cross-platform auto-launch at login for Linux: - Systemd user services backend (~/.config/systemd/user/) for host installations (deb/rpm/AppImage) via org.freedesktop.systemd1 D-Bus API with proper ExecStart quoting for paths containing spaces. - XDG Desktop Portal Background API for Flatpak sandboxes, with CLI marker injection for login-launch detection (portal provides no context signal). - Detection via /proc/self/cgroup parsing (systemd host) or CLI marker (flatpak). - Respects graphical-session.target ordering to ensure DISPLAY/WAYLAND_DISPLAY are exported before app launch, preventing HeadlessException. - Diagnostic output includes portal availability, unit state, cgroup paths for troubleshooting. New files: - LinuxAutoLaunch.kt: dispatcher routing to SystemdUserBackend or FlatpakPortalBackend - SystemdUserBackend.kt: manages ~/.config/systemd/user/<appId>.service lifecycle - FlatpakPortalBackend.kt: XDG portal RequestBackground API with marker-based detection - NativeAutoLaunchLinuxBridge.kt: JNI bridge exposing D-Bus and portal APIs - nucleus_autolaunch_linux.c: C implementation of D-Bus/portal native calls - build.sh: build script for Linux shared library compilation Modified files: - AutoLaunch.kt: added Linux dispatch in resolveBackend() - AutoLaunchConfig.kt: added backgroundReason field for portal prompt customization - build.gradle.kts: added buildNativeLinux task with prebuilt library detection - reachability-metadata.json: added NativeAutoLaunchLinuxBridge for GraalVM native-image
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
New
autolaunchruntime module: unified API —AutoLaunch.state / enable / disable / wasStartedAtLogin / openSystemSettings— across every supported packaging type on Windows, macOS, and Linux.Windows
StartupTaskAPI via WRL on a dedicated MTA thread (AWT EDT is STA, avoids message-pump-dependent async completion). TaskId auto-injected by the Gradle plugin whenappx { addAutoLaunchExtension = true }(exposed viaNucleusApp.startupTaskId, overridable viaAppXSettings.startupTaskId).HKCU\…\Run+StartupApproved\Runparity rule (flag & 1 == 1⇒DISABLED_BY_USER). Injects--nucleus-autostartCLI marker.sihost.exeas external parent (empirical finding on Win11 — MSIX startup activations route through Shell Infrastructure Host, nottaskhostw.exeas some docs suggest). Deterministic, no heuristics, no WinAppSDK / PSF dependency.nucleus_autolaunch.dll.macOS
SMAppService.mainApp(macOS 13+) — same pattern assindresorhus/LaunchAtLogin-Modern. Registers the main app itself as a login item (no helper app, no bundled plist, no build-time plugin changes). Entry appears under System Settings → General → Login Items → Open at Login (not "Allow in the Background").SMAppService.mainApplaunches (open feedback FB10207829). Empirical signal:launchdinjectsLaunchInstanceIDinto the environment of every managed job it spawns, includingSMAppService.mainApplogin items; Finder / Dock / Spotlight /open(1)launches go through LaunchServices and do not carry this variable. Deterministic, no heuristic scoring.service-managementmodule (no new native code).Linux
SystemdUserBackend: writes~/.config/systemd/user/<app>.serviceand toggles it viaorg.freedesktop.systemd1.Manager.EnableUnitFiles. Login detection viaINVOCATION_IDenv var injected by systemd for every unit invocation.FlatpakPortalBackend: the sandbox can't reach the host's systemd, so usesorg.freedesktop.portal.Background.RequestBackgroundwithautostart=true. Login detection via the--nucleus-autostartCLI marker passed through the portal'scommandline(safe becauseflatpak run <id>has no spaces — sidesteps the portal'sExec=quoting bug).nucleus_autolaunch_linux.so(x64 + aarch64),dlopen'd GLib — no compile-time dep.Shared
runtime/autolaunch.mdpage wired into mkdocs nav.build-natives.yaml(Windows x64/ARM64, Linux x64/aarch64); download + EXPECTED entries added to all 6 consumer workflows.Test plan
Windows
state()reportsDISABLED_BY_USER→enable()returnsBLOCKED_BY_USER→openSystemSettings()opensms-settings:startupapps.external parent = sihost.exe→ banner visible.external parent = explorer.exe→ no banner.StartupTaskState.DisabledByUserafter user toggled via Task Manager →enable()returnsBLOCKED_BY_USER(no re-enable attempt).StartupApprovedDISABLED_BY_USERnot overwritten.macOS
LaunchInstanceID present: true.LaunchInstanceID present: false.state()reportsDISABLED_BY_USER→enable()returnsBLOCKED_BY_USER.Linux
~/.config/systemd/user/<app>.servicewritten and enabled → logout/login → banner visible, diagnostic showsINVOCATION_ID present: true.commandline).disable()on both backends cleanly removes the unit / revokes the portal permission.Cross-cutting
./gradlew :autolaunch:testpasses (Windows-only tests).state()/enable()/disable()/wasStartedAtLogin()never throw; unsupported environments returnUNSUPPORTED/false.