Skip to content

Commit fe64564

Browse files
committed
docs(autolaunch): document Linux backend (systemd + Flatpak portal) and update indices
- Add nucleus.autolaunch to Runtime Modules table and docs/runtime/index.md - Document systemd user service and Flatpak Background portal backends on Linux - Update wasStartedAtLogin detection signals (macOS: AppleEvent + LaunchInstanceID) - Add complete Linux behavior section with unit generation and portal flow - Update UNSUPPORTED states and openSystemSettings() notes
1 parent 71c15f9 commit fe64564

3 files changed

Lines changed: 42 additions & 8 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ Each module is published independently to Maven Central — use them together or
114114
| `nucleus.taskbar-progress` | Cross-platform taskbar progress bar & attention requests |
115115
| `nucleus.global-hotkey` | System-wide keyboard shortcuts |
116116
| `nucleus.energy-manager` | Energy efficiency & screen-awake APIs |
117+
| `nucleus.autolaunch` | Start the app at user login — MSIX `StartupTask`, Win32 `HKCU\...\Run`, `SMAppService`, systemd user units, Flatpak portal |
117118
| `nucleus.native-ssl` | OS trust store integration |
118119
| `nucleus.native-http` | HTTP client with native SSL |
119120
| `nucleus.linux-hidpi` | Native HiDPI scale detection on Linux |
@@ -127,7 +128,6 @@ Modules planned for upcoming releases. Contributions welcome.
127128

128129
| Module | Description | macOS | Windows | Linux |
129130
|--------|-------------|-------|---------|-------|
130-
| `auto-launch` | Start the app at user login | `SMAppService` / `LaunchAgent` | Run registry / Startup folder | `.desktop` autostart |
131131
| `secure-storage` | Hardware-backed secret storage for tokens, passwords, keys | Keychain | Credential Manager / DPAPI | Secret Service (`libsecret`) |
132132
| `biometric-auth` | Prompt for fingerprint / face authentication | `LocalAuthentication` (Touch ID / Face ID) | Windows Hello | `fprintd` via D-Bus / polkit |
133133
| `share-sheet` | OS share sheet (URL, file, text) | `NSSharingService` | Windows `DataTransferManager` | xdg-desktop-portal `Share` |

docs/runtime/autolaunch.md

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ Nucleus auto-detects the runtime packaging at startup (via `ExecutableRuntime`)
1010
| Win32 (MSI / NSIS) | `HKCU\...\Run` + `HKCU\...\Explorer\StartupApproved\Run` | Process is not packaged |
1111
| macOS DMG / PKG | `SMAppService.mainApp` (macOS 13+) | Always routed to SMAppService — works for DMG (Developer ID) and PKG (Mac App Store, sandboxed) |
1212
| macOS < 13 | — (returns `UNSUPPORTED`) ||
13-
| Linux | — (no-op in this version) ||
13+
| Linux (deb / rpm / AppImage / dev) | systemd user service via `org.freedesktop.systemd1` | `ExecutableRuntime.isFlatpak() == false` |
14+
| Linux (Flatpak) | `org.freedesktop.portal.Background.RequestBackground` | `ExecutableRuntime.isFlatpak() == true` |
1415

1516
The runtime exposes a single unified API — consumers don't need to branch on packaging themselves.
1617

@@ -37,7 +38,7 @@ when (val state = AutoLaunch.state()) {
3738
AutoLaunchState.DISABLED_BY_USER -> AutoLaunch.openSystemSettings()
3839
AutoLaunchState.DISABLED_BY_POLICY,
3940
AutoLaunchState.ENABLED_BY_POLICY -> { /* read-only, GPO */ }
40-
AutoLaunchState.UNSUPPORTED -> { /* mac/linux */ }
41+
AutoLaunchState.UNSUPPORTED -> { /* macOS < 13, unsupported Linux env */ }
4142
}
4243
```
4344

@@ -65,7 +66,7 @@ Switch(
6566
| `isUserLocked()` | `Boolean` | `true` when state is `DISABLED_BY_USER` |
6667
| `enable()` | `AutoLaunchResult` | See rules below |
6768
| `disable()` | `AutoLaunchResult` ||
68-
| `openSystemSettings()` | `Boolean` | Opens `ms-settings:startupapps` on Windows; `com.apple.LoginItems-Settings.extension` on macOS |
69+
| `openSystemSettings()` | `Boolean` | Opens `ms-settings:startupapps` on Windows; `com.apple.LoginItems-Settings.extension` on macOS; no-op on Linux (no cross-DE "startup apps" URL) |
6970
| `wasStartedAtLogin(args)` | `Boolean` | `true` if the process was launched by auto-launch. Works on both Win32 and MSIX |
7071

7172
### `AutoLaunchState`
@@ -77,7 +78,7 @@ Switch(
7778
| `DISABLED_BY_USER` | **User toggled off via Task Manager / Settings — programmatic re-enable is blocked** |
7879
| `DISABLED_BY_POLICY` | Blocked by Group Policy (MSIX only) |
7980
| `ENABLED_BY_POLICY` | Forced on by Group Policy (MSIX only) |
80-
| `UNSUPPORTED` | Platform or packaging not supported (Linux, sandboxed macOS PKG) |
81+
| `UNSUPPORTED` | Platform or packaging not supported (macOS < 13, Linux without systemd / portal, missing JNI lib) |
8182

8283
### `AutoLaunchResult`
8384

@@ -181,14 +182,43 @@ After `enable()`, macOS may leave the service in `requiresApproval` until the us
181182

182183
### Detection of an auto-launched start
183184

184-
`AutoLaunch.wasStartedAtLogin(args)` on macOS reads the `keyAELaunchedAsLogInItem` marker carried by the `kAEOpenApplication` AppleEvent that `loginwindow` dispatches at login. The detection is independent of the CLI `args` parameter — it is kept for API symmetry with Windows, where a marker argument is injected into the launch entry.
185+
`AutoLaunch.wasStartedAtLogin(args)` on macOS combines two independent signals:
185186

186-
The native observer is installed at dylib-load time (`__attribute__((constructor))`), so it is in place before AWT's `NSApplication` starts its run loop and consumes the event. Call `AutoLaunch.wasStartedAtLogin(args)` anywhere in `main()` once the native library is loaded.
187+
1. The `keyAELaunchedAsLogInItem` marker carried by the `kAEOpenApplication` AppleEvent that `loginwindow` dispatches at login. The native observer is installed at dylib-load time (`__attribute__((constructor))`), so it is in place before AWT's `NSApplication` starts its run loop and consumes the event.
188+
2. The `LaunchInstanceID` environment variable that `launchd` injects into every process it spawns via `SMAppService`. Present at login-time start, absent on manual launches.
189+
190+
Either signal returning `true` is enough. The CLI `args` parameter is unused on macOS and kept for API symmetry with Windows.
187191

188192
### macOS < 13
189193

190194
`SMAppService` requires macOS 13.0+ (Ventura). On older releases, `AppServiceManager.isAvailable` returns `false` and every call reports `UNSUPPORTED` — there is no legacy fallback in the runtime.
191195

196+
## Linux behavior
197+
198+
Two backends, chosen at first access based on `ExecutableRuntime.isFlatpak()`. Both ride on a JNI bridge to GLib's GIO for D-Bus — no external library is linked at compile time.
199+
200+
### Host (deb / rpm / AppImage / dev runs) — systemd user service
201+
202+
`enable()` writes a transient unit at `~/.config/systemd/user/<app>.service`, calls `Reload` on `org.freedesktop.systemd1.Manager`, then `EnableUnitFiles` + `StartUnit`. `disable()` stops and disables the unit, then removes the file. `state()` is read from `ActiveState` / `UnitFileState` so the result reflects what systemd will actually do at next login — not just what is on disk.
203+
204+
The generated unit uses `Type=simple` with `WantedBy=default.target` and the process's own `ProcessHandle.current().info().command()` as `ExecStart` (override via `AutoLaunchConfig.executablePath`). The `--nucleus-autostart` marker is appended to `ExecStart` so `wasStartedAtLogin` works symmetrically with the other backends.
205+
206+
Login detection uses `INVOCATION_ID` — systemd injects it into every unit it spawns and it is not inherited across re-exec, so it is a reliable "started by systemd" signal even if the CLI marker is stripped.
207+
208+
### Flatpak — `org.freedesktop.portal.Background`
209+
210+
Inside a Flatpak sandbox, the user's systemd is unreachable — the portal is the only supported path. `enable()` calls `org.freedesktop.portal.Background.RequestBackground` with `autostart=true`, a `commandline` of `["flatpak", "run", "<app-id>", "--nucleus-autostart"]`, and the user-facing reason from `AutoLaunchConfig.backgroundReason` (default: `"Launch <appName> at login"`).
211+
212+
`state()` inspects the `~/.var/app/<id>/.../autostart/<id>.desktop` file exposed to the sandbox. `disable()` calls the same portal method with `autostart=false`. Both operations are asynchronous at the portal level; the bridge waits for the `Response` signal so calls are effectively synchronous from Kotlin.
213+
214+
### Dependencies
215+
216+
No extra module is required — the Linux native library ships inside `nucleus.autolaunch`. The backend uses `dlopen` on `libgio-2.0.so.0` at runtime, so the JAR runs unchanged on any modern Linux distribution (GLib 2.56+).
217+
218+
### `openSystemSettings()` on Linux
219+
220+
Returns `false`. There is no cross-desktop "startup apps" URL (GNOME, KDE, XFCE each manage autostart differently). Consumers that want to surface a shortcut can open `gnome-session-properties` or the distribution's equivalent themselves.
221+
192222
## Configuration overrides
193223

194224
All defaults fall back to `NucleusApp`. Override any of them **before** the first `AutoLaunch` call:
@@ -217,8 +247,10 @@ Detection is transparent across packaging types and uses the **signal that is de
217247
| Backend | Mechanism |
218248
|---|---|
219249
| Win32 (MSI / NSIS) | Looks for the marker argument (`--nucleus-autostart` by default, configurable via `AutoLaunchConfig.autostartArgument`) written into the `HKCU\...\Run` command line |
220-
| macOS user-dir LaunchAgent | Same marker argument, injected into the plist's `ProgramArguments` at `enable()` time |
250+
| macOS `SMAppService.mainApp` | Two signals — (1) the `kAEOpenApplication` AppleEvent carrying `keyAELaunchedAsLogInItem`, intercepted at dylib-load time before AWT consumes it; (2) the `LaunchInstanceID` env var that `launchd` injects into SMAppService-spawned processes. Either one fires `true` |
221251
| MSIX packaged desktop | Walks up the process tree (skipping self-spawned jpackage launcher chains) and checks if the external ancestor is `sihost.exe` — the Shell Infrastructure Host that launches MSIX startup-task activations. Manual launches (Start menu, Explorer, taskbar) come from `explorer.exe` or `runtimebroker.exe` |
252+
| Linux systemd user unit | Reads `INVOCATION_ID` — systemd injects it into every unit it spawns. Present at login-time start, absent on manual launches from a terminal or `.desktop` entry |
253+
| Linux Flatpak portal | Looks for the marker argument injected into the portal's `commandline` at `enable()` time. `flatpak run <app-id>` is a single token so the portal's historical `Exec=` quoting bug does not apply |
222254

223255
Both paths are fully deterministic — no heuristics, no timing guesses, no runtime dependencies beyond the shipped native DLL.
224256

docs/runtime/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Nucleus provides runtime libraries for use in your application code. All are pub
3434
| Linux HiDPI | `io.github.kdroidfilter:nucleus.linux-hidpi` | Native HiDPI scale factor detection on Linux |
3535
| Scheduler | `io.github.kdroidfilter:nucleus.scheduler` | Background task scheduling — periodic, calendar, and on-boot tasks via OS-native schedulers (launchd, systemd, Task Scheduler) |
3636
| Service Management (macOS) | `io.github.kdroidfilter:nucleus.service-management-macos` | macOS SMAppService binding — login items, launch agents, launch daemons (macOS 13+) |
37+
| Auto-Launch | `io.github.kdroidfilter:nucleus.autolaunch` | Cross-platform start-at-login — MSIX `StartupTask`, Win32 `HKCU\...\Run`, `SMAppService`, systemd user units, Flatpak portal |
3738
| GraalVM Runtime | `io.github.kdroidfilter:nucleus.graalvm-runtime` | GraalVM native-image bootstrap + font substitutions (includes linux-hidpi) |
3839

3940
```kotlin
@@ -67,6 +68,7 @@ dependencies {
6768
implementation("io.github.kdroidfilter:nucleus.linux-hidpi:<version>")
6869
implementation("io.github.kdroidfilter:nucleus.scheduler:<version>")
6970
implementation("io.github.kdroidfilter:nucleus.service-management-macos:<version>")
71+
implementation("io.github.kdroidfilter:nucleus.autolaunch:<version>")
7072
implementation("io.github.kdroidfilter:nucleus.graalvm-runtime:<version>")
7173
}
7274
```

0 commit comments

Comments
 (0)