feat(application): cross-platform Autostart manager - #5426
Conversation
Adds app.Autostart for registering the application to launch at user
login. Picks the right native mechanism per platform and falls back
cleanly when one is unavailable.
Mechanisms:
- macOS 13+ bundled .app: SMAppService.mainAppService — works for
sandboxed / Mac App Store apps, no TCC automation prompt (the
historical AppleScript approach triggered one)
- macOS older / unbundled: ~/Library/LaunchAgents/<id>.plist with
RunAtLoad=true, activated via launchctl bootstrap
- Windows: HKCU\Software\Microsoft\Windows\CurrentVersion\Run, with
CommandLineToArgvW-style quoting for paths with spaces
- Linux: $XDG_CONFIG_HOME/autostart/<id>.desktop with freedesktop-spec
Exec= escaping
- Android / iOS / server: ErrAutostartNotSupported (use errors.Is)
API:
app.Autostart.Enable()
app.Autostart.EnableWithOptions(AutostartOptions{Identifier, Arguments})
app.Autostart.Disable()
app.Autostart.IsEnabled() (bool, error)
app.Autostart.Status() (AutostartStatus, error)
Design notes:
- All on-disk artefacts (.plist, .desktop) are written via tempfile +
os.Rename so a partial write never leaves a half-formed file.
- os.Executable() is resolved through filepath.EvalSymlinks so
registrations survive symlink-based installs (Homebrew, Scoop).
- Disable() and Status() match the registered EXE path rather than
the stored identifier, so renaming the binary or changing the
identifier between releases doesn't strand the registration.
- Test seams (launchctlBootstrap/Bootout as package vars, Windows
registry sub-key field) keep unit tests from accidentally launching
the test binary on the host (a real risk with RunAtLoad=true).
Includes:
- examples/autostart/ — runnable status/enable/disable demo
- docs/features/autostart/basics.mdx — feature reference
- docs/concepts/manager-api.mdx — Autostart added to the manager list
- docs/astro.config.mjs — sidebar entry
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
🚧 Files skipped from review as they are similar to previous changes (4)
WalkthroughAdds a cross-platform AutostartManager and implementations for macOS (SMAppService + LaunchAgent), Windows (Registry Run), and Linux (XDG .desktop), plus docs, an example app, and unit tests; autostart options, status reporting, and identifier handling are exposed via the new API. ChangesAutostart Feature Implementation
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 golangci-lint (2.12.2)level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies" Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Adds a new app.Autostart manager to the v3 application API, providing cross-platform enable/disable/status for launching the app at user login on macOS, Windows, and Linux, with platform stubs returning ErrAutostartNotSupported for unsupported targets.
Changes:
- Introduces
AutostartManagerpublic API plus shared helpers/types (AutostartOptions,AutostartStatus, strategy enum). - Implements per-OS autostart registration (LaunchAgent/SMAppService on macOS, HKCU Run key on Windows, XDG
.desktopon Linux) and adds unit tests (darwin/linux + shared helpers). - Adds an example app and documentation pages/sidebar entries for the new feature.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| v3/pkg/application/autostart.go | Shared helpers (resolved executable, atomic writes), identifier validation, shared types/status/strategy. |
| v3/pkg/application/autostart_manager.go | Public app.Autostart API surface (Enable/Disable/IsEnabled/Status). |
| v3/pkg/application/autostart_windows.go | Windows implementation using HKCU\...\Run registry values + quoting/parsing helpers. |
| v3/pkg/application/autostart_linux.go | Linux implementation writing XDG autostart .desktop files. |
| v3/pkg/application/autostart_darwin.go | macOS implementation using SMAppService where available and LaunchAgent fallback. |
| v3/pkg/application/autostart_darwin_smappservice.go | CGO bridge to SMAppService.mainAppService register/unregister/status. |
| v3/pkg/application/autostart_android.go | Android stub returning ErrAutostartNotSupported. |
| v3/pkg/application/autostart_ios.go | iOS stub returning ErrAutostartNotSupported. |
| v3/pkg/application/autostart_server.go | Server build stub returning ErrAutostartNotSupported. |
| v3/pkg/application/autostart_test.go | Unit tests for slugging, identifier validation, and atomic writes helper. |
| v3/pkg/application/autostart_linux_test.go | Linux unit tests for round-trip and escaping/parsing helpers. |
| v3/pkg/application/autostart_darwin_test.go | macOS unit tests for LaunchAgent round-trip + plist parsing helper. |
| v3/pkg/application/application.go | Wires AutostartManager onto App and initializes it. |
| v3/examples/autostart/main.go | Runnable demo showing enable/disable/status via menu actions. |
| docs/src/content/docs/features/autostart/basics.mdx | New feature documentation page covering API + per-platform behavior. |
| docs/src/content/docs/concepts/manager-api.mdx | Adds app.Autostart to the Manager API concept doc + usage snippet. |
| docs/astro.config.mjs | Adds Autostart section to docs sidebar autogeneration. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if _, err := tmp.Write(data); err != nil { | ||
| _ = tmp.Close() | ||
| cleanup() | ||
| return err | ||
| } |
There was a problem hiding this comment.
Fixed in 0d67b58. Added a defensive n == len(data) check after tmp.Write returning io.ErrShortWrite if they disagree. os.File.Write is documented to fail-fast on short writes so this is belt-and-braces, but it removes the silent-truncation risk if the writer type ever changes.
| // AutostartStrategy names the underlying mechanism a darwin registration used. | ||
| // It is empty on other platforms. | ||
| type AutostartStrategy string | ||
|
|
||
| const ( | ||
| AutostartStrategyNone AutostartStrategy = "" | ||
| AutostartStrategySMAppService AutostartStrategy = "smappservice" | ||
| AutostartStrategyLaunchAgent AutostartStrategy = "launchagent" | ||
| AutostartStrategyRegistryRun AutostartStrategy = "registry-run" | ||
| AutostartStrategyXDGAutostart AutostartStrategy = "xdg-autostart" |
There was a problem hiding this comment.
Fixed in 0d67b58. Rewrote the doc comment to say AutostartStrategy is used cross-platform, with darwin distinguishing between SMAppService and the LaunchAgent fallback, and Windows/Linux each having a single fixed strategy.
| // | ||
| // If empty, a sensible default is derived: on macOS the application's | ||
| // bundle identifier (when running from a bundle) or "wails.autostart.<slug>"; | ||
| // on Windows and Linux a slugified form of Options.Name. |
There was a problem hiding this comment.
Fixed in 0d67b58. Reworded to make clear it's the application's application.Options.Name (from application.New), not a non-existent AutostartOptions.Name field.
| // quoteExec escapes a single Exec field token per the freedesktop.org spec: | ||
| // reserved chars are " ` $ \ → escape with backslash; if the token contains | ||
| // any reserved or whitespace, double-quote it. | ||
| func quoteExec(s string) string { | ||
| needQuote := false | ||
| var b strings.Builder | ||
| for _, r := range s { | ||
| switch r { | ||
| case '"', '`', '$', '\\': | ||
| b.WriteByte('\\') | ||
| b.WriteRune(r) | ||
| needQuote = true | ||
| case ' ', '\t', '\n': | ||
| b.WriteRune(r) | ||
| needQuote = true | ||
| default: | ||
| b.WriteRune(r) | ||
| } | ||
| } | ||
| if needQuote { | ||
| return `"` + b.String() + `"` | ||
| } | ||
| return b.String() |
There was a problem hiding this comment.
Fixed in 0d67b58. Added validateDesktopExecToken that rejects ASCII control chars (everything below 0x20 plus 0x7F, but keeping tab and space which quoteExec handles via double-quoting). enable() now runs it against the resolved executable path and every argument before they reach quoteExec, so a newline-bearing user argument can't inject additional Desktop Entry keys. Also removed the now-dead '\n' case from quoteExec itself as a defence-in-depth measure.
| /* | ||
| #cgo CFLAGS: -mmacosx-version-min=10.15 -x objective-c -Wno-unguarded-availability-new | ||
| #cgo LDFLAGS: -framework Foundation -framework ServiceManagement | ||
|
|
There was a problem hiding this comment.
Fixed in 0d67b58. Added #include <stdlib.h> (for free) and #include <string.h> (for strdup) to the C preamble.
| ## Identifier | ||
|
|
||
| If `Options.Identifier` is empty, a default is derived from your app's name: | ||
|
|
||
| | Platform | Default identifier | | ||
| |---|---| | ||
| | macOS (bundled) | The app's bundle identifier, e.g. `com.example.MyApp` | | ||
| | macOS (unbundled) | `wails.autostart.<slug-of-options.Name>` | | ||
| | Windows | Slugified `Options.Name` (lowercase, non-`A-Za-z0-9._-` stripped, spaces become dashes) | |
There was a problem hiding this comment.
Fixed in 0d67b58. Changed every Options.Identifier reference in the docs to AutostartOptions.Identifier so readers don't go looking for it on the main app Options struct.
| - If the user renames the binary, autostart still finds the registration and cleans it up correctly. | ||
| - If the identifier was changed between releases, the old registration is still discoverable by `Status()` and can still be cleaned up by `Disable()`. |
There was a problem hiding this comment.
Fixed in 0d67b58. You're right — the old wording oversold the guarantee. The new section says explicitly: identifier changes are safe (we find by path); symlinked-install changes are safe (filepath.EvalSymlinks is applied before matching); but arbitrary binary moves leave an orphaned entry, and apps shipped as portable binaries should either Disable() before moving themselves or launch through a stable symlink.
| // parseWindowsCommandExe returns the first token of a Windows command line, | ||
| // honouring surrounding double quotes for paths with spaces. Returned in | ||
| // lowercase for case-insensitive comparison. | ||
| func parseWindowsCommandExe(cmd string) string { | ||
| cmd = strings.TrimSpace(cmd) | ||
| if cmd == "" { | ||
| return "" | ||
| } | ||
| if cmd[0] == '"' { | ||
| end := strings.IndexByte(cmd[1:], '"') | ||
| if end < 0 { | ||
| return strings.ToLower(cmd[1:]) | ||
| } | ||
| return strings.ToLower(cmd[1 : 1+end]) | ||
| } | ||
| if i := strings.IndexAny(cmd, " \t"); i >= 0 { | ||
| return strings.ToLower(cmd[:i]) | ||
| } | ||
| return strings.ToLower(cmd) | ||
| } | ||
|
|
||
| // quoteWindowsArg wraps an argument in double quotes when it contains | ||
| // whitespace or quotes, and escapes embedded quotes. Backslashes preceding a | ||
| // quote are doubled per CommandLineToArgvW rules. | ||
| func quoteWindowsArg(s string) string { | ||
| if s != "" && !strings.ContainsAny(s, ` " `) { | ||
| return s | ||
| } | ||
| var b strings.Builder | ||
| b.WriteByte('"') | ||
| backslashes := 0 | ||
| for i := 0; i < len(s); i++ { | ||
| c := s[i] | ||
| switch c { | ||
| case '\\': | ||
| backslashes++ | ||
| case '"': | ||
| for j := 0; j < backslashes; j++ { | ||
| b.WriteByte('\\') | ||
| } | ||
| b.WriteByte('\\') | ||
| b.WriteByte('"') | ||
| backslashes = 0 | ||
| default: | ||
| for j := 0; j < backslashes; j++ { | ||
| b.WriteByte('\\') | ||
| } | ||
| backslashes = 0 | ||
| b.WriteByte(c) | ||
| } | ||
| } | ||
| for j := 0; j < backslashes; j++ { | ||
| b.WriteByte('\\') | ||
| b.WriteByte('\\') | ||
| } | ||
| b.WriteByte('"') | ||
| return b.String() | ||
| } |
There was a problem hiding this comment.
Fixed in 0d67b58. Added autostart_windows_test.go covering registry round-trip, custom identifier + arguments quoting, identifier validation rejection, disable noop, parseWindowsCommandExe quoted/unquoted/leading-whitespace/empty cases, and quoteWindowsArg for the tricky CommandLineToArgvW backslash-doubling rules. Tests use the existing registrySubKey field as a test seam, writing under HKCU\\Software\\Wails\\Tests\\Autostart-<t.Name()> with t.Cleanup deleting the subkey afterwards — production HKCU\\…\\Run is never touched. Can't run them locally (macOS host) but GOOS=windows go vet is clean; they'll execute on the Wails Windows CI.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
v3/examples/autostart/main.go (1)
19-40: ⚡ Quick winUse
app.Dialog.Error()for error messages.Lines 22, 32, and 38 display errors using
app.Dialog.Info(). Error conditions should useapp.Dialog.Error()to provide appropriate visual feedback (error icon, error styling).💬 Suggested improvement
menu.Add("Status").OnClick(func(_ *application.Context) { st, err := app.Autostart.Status() if err != nil { - app.Dialog.Info().SetMessage(fmt.Sprintf("Error: %v", err)).Show() + app.Dialog.Error().SetTitle("Status Error").SetMessage(fmt.Sprintf("%v", err)).Show() return } msg := fmt.Sprintf("Enabled: %v\nStrategy: %s\nPath: %s", st.Enabled, st.Strategy, st.Path) app.Dialog.Info().SetMessage(msg).Show() }) menu.Add("Enable").OnClick(func(_ *application.Context) { if err := app.Autostart.Enable(); err != nil { - app.Dialog.Info().SetMessage(fmt.Sprintf("Enable failed: %v", err)).Show() + app.Dialog.Error().SetTitle("Enable Failed").SetMessage(fmt.Sprintf("%v", err)).Show() } }) menu.Add("Disable").OnClick(func(_ *application.Context) { if err := app.Autostart.Disable(); err != nil { - app.Dialog.Info().SetMessage(fmt.Sprintf("Disable failed: %v", err)).Show() + app.Dialog.Error().SetTitle("Disable Failed").SetMessage(fmt.Sprintf("%v", err)).Show() } })🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@v3/examples/autostart/main.go` around lines 19 - 40, Replace the uses of app.Dialog.Info() in the error branches with app.Dialog.Error() so error dialogs use the proper styling; specifically update the Status handler error branch (after app.Autostart.Status() returns err) and the Enable and Disable handlers (after app.Autostart.Enable() and app.Autostart.Disable() return err) to call app.Dialog.Error().SetMessage(...).Show() instead of app.Dialog.Info().SetMessage(...).Show().v3/pkg/application/autostart_windows.go (1)
30-58: ⚡ Quick win
enable()doesn't clean up an existing entry written under a different value name.If
Enable()is called once withIdentifier="v1"and later withIdentifier="v2"(or the slug derived fromapp.options.Namechanges between releases), bothHKCU\…\Run\v1andHKCU\…\Run\v2will end up pointing at the current binary.disable()walksReadValueNamesand only removes the first match, leaving a stale autostart entry behind.Consider calling
find()first and deleting any matching pre-existing value whose name differs fromidbeforeSetStringValue:♻️ Proposed refactor
cmd := quoteWindowsArg(exe) for _, arg := range opts.Arguments { cmd += " " + quoteWindowsArg(arg) } key, _, err := registry.CreateKey(registry.CURRENT_USER, a.registrySubKey, registry.SET_VALUE) if err != nil { return fmt.Errorf("autostart: open registry key: %w", err) } defer key.Close() + // Remove any stale entry registered under a different value name. + if existing, _, err := a.find(); err == nil && existing != "" && existing != id { + _ = key.DeleteValue(existing) + } if err := key.SetStringValue(id, cmd); err != nil { return fmt.Errorf("autostart: write registry value: %w", err) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@v3/pkg/application/autostart_windows.go` around lines 30 - 58, The enable() routine can leave stale registry values when the autostart identifier changes; before writing the new value with key.SetStringValue(id, cmd) call find() (or use registry.Key.ReadValueNames) to enumerate existing Run entries, delete any matching autostart entry whose name != id (using key.DeleteValue(name)) so old names (e.g., previous Identifier values or old autostartSlug names) are removed, then proceed to SetStringValue; mirror this logic with the existing find()/disable() helpers to ensure consistent identification and cleanup.v3/pkg/application/autostart_darwin.go (1)
244-259: 💤 Low valueDead plist types —
plistDoc,plistDict,plistElare unused.The XML is produced manually via
strings.BuilderinlaunchAgentPlist(lines 261‑283), so these struct declarations are not referenced anywhere in the file or the test file. Consider removing them, or wirelaunchAgentPlistto usexml.Marshalagainst them — but not both.🧹 Proposed cleanup
-// --- plist marshalling ------------------------------------------------------ - -type plistDoc struct { - XMLName xml.Name `xml:"plist"` - Version string `xml:"version,attr"` - Dict plistDict `xml:"dict"` -} - -type plistDict struct { - Keys []string `xml:"-"` - Values []plistEl `xml:"-"` -} - -type plistEl struct { - Kind string // "string", "true", "false", "array" - String string - Array []string // for "array" of strings -} - +// --- plist marshalling ------------------------------------------------------🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@v3/pkg/application/autostart_darwin.go` around lines 244 - 259, The types plistDoc, plistDict, and plistEl are dead code because launchAgentPlist builds the XML manually; either remove these unused struct declarations to clean up the file or modify launchAgentPlist to construct a plistDoc (with plistDict/plistEl children) and serialize it with xml.Marshal instead of manual string building; locate the symbols plistDoc, plistDict, plistEl and the launchAgentPlist function and choose one approach (delete the structs or wire launchAgentPlist to use xml.Marshal) to eliminate the inconsistency.v3/pkg/application/autostart_linux.go (1)
20-49: ⚡ Quick win
enable()leaves stale.desktopfiles behind when the identifier changes.Mirrors the Windows issue: if
Enable()is called once with no identifier (uses the slug ofapp.options.Name), and a later release renames the app or supplies a customIdentifier, both.desktopfiles end up pointing at the sameexe.findDesktopFile()then returns whichever is first inos.ReadDir, sodisable()only removes one andstatus()reportsEnabled=trueindefinitely (with the user-visiblePatharbitrarily one or the other).Consider sweeping out any pre-existing matching entry whose filename differs from the new
idbefore writing:♻️ Proposed refactor
id := opts.Identifier if id == "" { id = autostartSlug(a.app.options.Name) } path := filepath.Join(dir, id+".desktop") + // Remove any stale autostart entry registered under a different identifier + // that points at the same executable. + if existing, err := a.findDesktopFile(dir); err == nil && existing != "" && existing != path { + _ = os.Remove(existing) + } + body := buildDesktopEntry(a.app.options.Name, exe, opts.Arguments) if err := writeFileAtomic(path, []byte(body), 0644); err != nil { return fmt.Errorf("write desktop file %s: %w", path, err) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@v3/pkg/application/autostart_linux.go` around lines 20 - 49, The enable() implementation can leave stale .desktop files when the identifier changes; before writing the new desktop file, scan the autostart directory (use autostartDir()/os.ReadDir) for existing .desktop files whose Exec (or full desktop entry contents) points to the same resolved executable (resolvedExecutable()) and, if their filename != new id+".desktop", remove them so only the intended id remains; do this sweep in enable() prior to writeFileAtomic(path, ...) so findDesktopFile()/disable() and status() see a single consistent entry (mirror the fix used for Windows identifiers and ensure you reference autostartSlug(a.app.options.Name) when computing the fallback id).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@v3/pkg/application/autostart_darwin.go`:
- Around line 75-104: The status() method currently treats
errSMAppServiceRequiresApproval from smAppServiceIsEnabled() as a fatal error;
update status() so that it treats errSMAppServiceRequiresApproval the same as
errSMAppServiceUnavailable (i.e., do not return an error, just treat the
SMAppService as "not enabled" and continue to the LaunchAgent fallback),
ensuring smAppServiceIsEnabled() errors are only propagated for unexpected
errors; adjust the guard around the smAppServiceIsEnabled() call to allow both
errSMAppServiceUnavailable and errSMAppServiceRequiresApproval to be ignored and
still call findLaunchAgent() and return the appropriate AutostartStatus.
In `@v3/pkg/application/autostart_windows.go`:
- Around line 154-187: The quoteWindowsArg function mis-escapes backslashes
before a quote: when handling case '"' it currently writes only backslashes+1
slashes, but per CommandLineToArgvW you must emit 2*backslashes+1 backslashes
before a literal quote; update the '"' branch in quoteWindowsArg to write
2*backslashes backslashes plus the extra one (so total 2*backslashes+1) before
writing the quote, keep the existing handling for other characters and the
trailing-backslash loop, and add Windows unit tests for inputs like `a\"b`,
`a\\"b`, and a trailing `\\` to prevent regressions.
---
Nitpick comments:
In `@v3/examples/autostart/main.go`:
- Around line 19-40: Replace the uses of app.Dialog.Info() in the error branches
with app.Dialog.Error() so error dialogs use the proper styling; specifically
update the Status handler error branch (after app.Autostart.Status() returns
err) and the Enable and Disable handlers (after app.Autostart.Enable() and
app.Autostart.Disable() return err) to call
app.Dialog.Error().SetMessage(...).Show() instead of
app.Dialog.Info().SetMessage(...).Show().
In `@v3/pkg/application/autostart_darwin.go`:
- Around line 244-259: The types plistDoc, plistDict, and plistEl are dead code
because launchAgentPlist builds the XML manually; either remove these unused
struct declarations to clean up the file or modify launchAgentPlist to construct
a plistDoc (with plistDict/plistEl children) and serialize it with xml.Marshal
instead of manual string building; locate the symbols plistDoc, plistDict,
plistEl and the launchAgentPlist function and choose one approach (delete the
structs or wire launchAgentPlist to use xml.Marshal) to eliminate the
inconsistency.
In `@v3/pkg/application/autostart_linux.go`:
- Around line 20-49: The enable() implementation can leave stale .desktop files
when the identifier changes; before writing the new desktop file, scan the
autostart directory (use autostartDir()/os.ReadDir) for existing .desktop files
whose Exec (or full desktop entry contents) points to the same resolved
executable (resolvedExecutable()) and, if their filename != new id+".desktop",
remove them so only the intended id remains; do this sweep in enable() prior to
writeFileAtomic(path, ...) so findDesktopFile()/disable() and status() see a
single consistent entry (mirror the fix used for Windows identifiers and ensure
you reference autostartSlug(a.app.options.Name) when computing the fallback id).
In `@v3/pkg/application/autostart_windows.go`:
- Around line 30-58: The enable() routine can leave stale registry values when
the autostart identifier changes; before writing the new value with
key.SetStringValue(id, cmd) call find() (or use registry.Key.ReadValueNames) to
enumerate existing Run entries, delete any matching autostart entry whose name
!= id (using key.DeleteValue(name)) so old names (e.g., previous Identifier
values or old autostartSlug names) are removed, then proceed to SetStringValue;
mirror this logic with the existing find()/disable() helpers to ensure
consistent identification and cleanup.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 51ba7306-d7fe-4bcc-b6a6-7e7af20d2c19
📒 Files selected for processing (17)
docs/astro.config.mjsdocs/src/content/docs/concepts/manager-api.mdxdocs/src/content/docs/features/autostart/basics.mdxv3/examples/autostart/main.gov3/pkg/application/application.gov3/pkg/application/autostart.gov3/pkg/application/autostart_android.gov3/pkg/application/autostart_darwin.gov3/pkg/application/autostart_darwin_smappservice.gov3/pkg/application/autostart_darwin_test.gov3/pkg/application/autostart_ios.gov3/pkg/application/autostart_linux.gov3/pkg/application/autostart_linux_test.gov3/pkg/application/autostart_manager.gov3/pkg/application/autostart_server.gov3/pkg/application/autostart_test.gov3/pkg/application/autostart_windows.go
- autostart.go: defensive short-write check in writeFileAtomic (n vs len(data)); doc comment for AutostartStrategy clarifies it is used cross-platform, not darwin-only; AutostartOptions.Identifier doc clarifies the Name source is application.Options.Name. - autostart_linux.go: validateDesktopExecToken rejects ASCII control characters in the executable path and arguments before they reach quoteExec, preventing a newline-bearing argument from injecting additional Desktop Entry keys into the .desktop file. - autostart_darwin_smappservice.go: explicit <stdlib.h> and <string.h> includes for strdup/free so the C preamble compiles even when toolchain flags would otherwise warn on implicit declarations. - autostart_windows_test.go: registry round-trip, custom identifier, identifier validation, disable noop, parseWindowsCommandExe, and quoteWindowsArg cases — using the existing test seam to read/write under HKCU\\Software\\Wails\\Tests\\Autostart-<t.Name()> so prod HKCU\\…\\Run is never touched. - docs/features/autostart/basics.mdx: rename Options.Identifier to AutostartOptions.Identifier; rewrite the stale-detection section so it accurately describes path-based matching — identifier and symlinked-install changes are handled; arbitrary binary moves leave an orphaned entry.
|
Addressed all 8 of @copilot-pull-request-reviewer's inline comments in `0d67b58`:
Verified: darwin build + tests pass; linux build + tests pass on `lin-node1`; `GOOS=windows go vet` clean. Each Copilot comment also has an inline reply pointing to the specific change. |
Two actionable findings + four nitpicks: 1. (Major) quoteWindowsArg mis-escaped a backslash immediately preceding a literal quote: emitted (N+1) backslashes when CommandLineToArgvW requires (2N+1). For input `a\"b` the old output `"a\\"b"` would decode as `a\` (the quote toggled state and was lost) instead of `a\"b`. The existing Windows test cases already encoded the correct 2N+1 expectations, so they begin passing with this fix. 2. (Minor) darwin status() treated errSMAppServiceRequiresApproval (the user disabled the login item in System Settings) as a fatal error, skipping the LaunchAgent fallback. Now tolerated alongside errSMAppServiceUnavailable so the fallback can still find a legacy LaunchAgent. 3. (Nit) autostart_windows.go and autostart_linux.go enable() now sweep any pre-existing autostart entry that points at this binary under a different identifier before writing the new one. Without this, calling Enable() twice with different Identifiers (or with a slug that changed because Options.Name was renamed between releases) left a stale duplicate behind. 4. (Nit) Removed dead plistDoc / plistDict / plistEl types from autostart_darwin.go — leftover from an iteration that used xml.Marshal before switching to manual string building. 5. (Nit) examples/autostart/main.go uses Dialog.Error() for failure branches instead of Dialog.Info(), giving error dialogs the expected icon + styling.
|
Addressed all six of @coderabbitai's findings (2 actionable + 4 nitpicks) in `51eebc9`:
Verified: darwin build + tests pass; linux build + tests pass on lin-node1; `GOOS=windows go vet` clean. Each actionable comment also has an inline reply pointing at the specific commit. |
|
I'll trigger a fresh review to check the full state of 🐇 ✨ ✅ Actions performedReview triggered.
|
Summary
Adds `app.Autostart` for registering the application to launch at user login on macOS, Windows, and Linux. Closes the long-standing gap in the manager API and supersedes the AppleScript-only approach floated in earlier drafts (#3910, #4474) — that approach triggered the macOS automation TCC prompt and didn't work for sandboxed/MAS apps.
API
`AutostartOptions` has two fields: `Identifier` (overrides the auto-derived registration ID — registry value name on Windows, plist Label on macOS, `.desktop` filename on Linux) and `Arguments` (appended to the executable path on launch).
`AutostartStatus.Strategy` names the mechanism used so apps can present meaningful diagnostics (`smappservice`, `launchagent`, `registry-run`, `xdg-autostart`).
Per-platform mechanism
Design notes
What's included
Test plan
Closes
Resolves the `app.Autostart` slot in the manager API. Earlier closed drafts: #3910, #4474 (both AppleScript-only macOS, didn't work for sandboxed apps).
Summary by CodeRabbit
New Features
Documentation
Tests