feat(application): cross-platform system sleep/wake events#5423
feat(application): cross-platform system sleep/wake events#5423leaanthony wants to merge 3 commits into
Conversation
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (14)
WalkthroughThis PR extends the Wails application event system with cross-platform system sleep and wake notifications. It defines new event types for Common, Linux, and macOS platforms, implements platform-specific signal handlers using NSWorkspace on macOS and DBus systemd-logind on Linux, and maps all platform-specific events to unified common event types. ChangesSystem Sleep/Wake Events
🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested labels
Poem
🚥 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.
Actionable comments posted: 2
🤖 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/internal/term/term.go`:
- Around line 105-110: The Error function currently returns early when
outputEnabled is false, which can suppress fatal error reporting; update the
Error implementation (function Error) to always emit the error output regardless
of the outputEnabled flag (i.e., remove or bypass the `if !outputEnabled {
return }` guard) so that calls like term.Error(err) always print the error
indicator and message; keep the existing formatting (col(ansiRed, "✗") and
sprint(input)) but do not gate it on outputEnabled.
- Around line 206-222: The HeaderTable rendering can panic when rows are ragged
because widths for extra columns default to 0, causing strings.Repeat to get a
negative count; in the HeaderTable logic (the loops that build widths and then
compose lines using widths and padded := cell + strings.Repeat(" ",
w-len(cell))), ensure the pad count is never negative by computing w safely
(e.g., when j >= len(widths) treat w as len(cell) or use max(widths[j],
len(cell))) or clamp pad := max(0, w-len(cell)) before calling strings.Repeat;
update the code paths that set w and build padded strings in the
HeaderTable/term table rendering to avoid negative repeat counts.
🪄 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: 7742c1a9-75ea-40a0-ac86-61ff0841f6a3
📒 Files selected for processing (28)
v3/cmd/wails3/main.gov3/go.modv3/internal/commands/appimage.gov3/internal/commands/capabilities.gov3/internal/commands/entitlements_setup.gov3/internal/commands/init.gov3/internal/commands/sign.gov3/internal/commands/signing_setup.gov3/internal/commands/update_cli.gov3/internal/doctor/doctor.gov3/internal/generator/analyse_test.gov3/internal/generator/collect/known_events.gov3/internal/generator/config/log.gov3/internal/runtime/desktop/@wailsio/runtime/src/event_types.tsv3/internal/templates/templates.gov3/internal/term/term.gov3/pkg/application/application_darwin.gov3/pkg/application/application_darwin_delegate.mv3/pkg/application/application_linux.gov3/pkg/application/events_common_darwin.gov3/pkg/application/events_common_linux.gov3/pkg/application/events_common_windows.gov3/pkg/events/events.gov3/pkg/events/events.txtv3/pkg/events/events_darwin.hv3/pkg/events/events_ios.hv3/pkg/events/events_linux.hv3/pkg/events/known_events.go
| func Error(input any) { | ||
| if !outputEnabled { | ||
| return | ||
| } | ||
| fmt.Println(" " + col(ansiRed, "✗") + " " + sprint(input)) | ||
| } |
There was a problem hiding this comment.
Do not gate fatal error output behind outputEnabled.
At Line 106, Error() returns early when output is disabled. Since main() now reports command failures via term.Error(err), quiet mode can hide fatal errors entirely.
Proposed fix
func Error(input any) {
- if !outputEnabled {
- return
- }
- fmt.Println(" " + col(ansiRed, "✗") + " " + sprint(input))
+ fmt.Fprintln(os.Stderr, " "+col(ansiRed, "✗")+" "+sprint(input))
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| func Error(input any) { | |
| if !outputEnabled { | |
| return | |
| } | |
| fmt.Println(" " + col(ansiRed, "✗") + " " + sprint(input)) | |
| } | |
| func Error(input any) { | |
| fmt.Fprintln(os.Stderr, " "+col(ansiRed, "✗")+" "+sprint(input)) | |
| } |
🤖 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/internal/term/term.go` around lines 105 - 110, The Error function
currently returns early when outputEnabled is false, which can suppress fatal
error reporting; update the Error implementation (function Error) to always emit
the error output regardless of the outputEnabled flag (i.e., remove or bypass
the `if !outputEnabled { return }` guard) so that calls like term.Error(err)
always print the error indicator and message; keep the existing formatting
(col(ansiRed, "✗") and sprint(input)) but do not gate it on outputEnabled.
| widths := make([]int, len(rows[0])) | ||
| for _, row := range rows { | ||
| for i, cell := range row { | ||
| if i < len(widths) && len(cell) > widths[i] { | ||
| widths[i] = len(cell) | ||
| } | ||
| } | ||
| } | ||
| for i, row := range rows { | ||
| line := " " | ||
| for j, cell := range row { | ||
| w := 0 | ||
| if j < len(widths) { | ||
| w = widths[j] | ||
| } | ||
| padded := cell + strings.Repeat(" ", w-len(cell)) | ||
| if i == 0 { |
There was a problem hiding this comment.
HeaderTable can panic on ragged rows with extra columns.
At Line 221, strings.Repeat(" ", w-len(cell)) can receive a negative count when a data row has more columns than the header row (w defaults to 0 for out-of-range columns).
Proposed fix
func HeaderTable(rows [][]string) {
if !outputEnabled || len(rows) == 0 {
return
}
- widths := make([]int, len(rows[0]))
+ maxCols := 0
+ for _, row := range rows {
+ if len(row) > maxCols {
+ maxCols = len(row)
+ }
+ }
+ widths := make([]int, maxCols)
for _, row := range rows {
for i, cell := range row {
if i < len(widths) && len(cell) > widths[i] {
widths[i] = len(cell)
}
}
}
for i, row := range rows {
line := " "
for j, cell := range row {
w := 0
if j < len(widths) {
w = widths[j]
}
- padded := cell + strings.Repeat(" ", w-len(cell))
+ pad := w - len(cell)
+ if pad < 0 {
+ pad = 0
+ }
+ padded := cell + strings.Repeat(" ", pad)
if i == 0 {
line += col(ansiBold, padded) + " "
} else {
line += padded + " "
}
}🤖 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/internal/term/term.go` around lines 206 - 222, The HeaderTable rendering
can panic when rows are ragged because widths for extra columns default to 0,
causing strings.Repeat to get a negative count; in the HeaderTable logic (the
loops that build widths and then compose lines using widths and padded := cell +
strings.Repeat(" ", w-len(cell))), ensure the pad count is never negative by
computing w safely (e.g., when j >= len(widths) treat w as len(cell) or use
max(widths[j], len(cell))) or clamp pad := max(0, w-len(cell)) before calling
strings.Repeat; update the code paths that set w and build padded strings in the
HeaderTable/term table rendering to avoid negative repeat counts.
There was a problem hiding this comment.
Pull request overview
This PR adds cross-platform system sleep/wake lifecycle events to the v3 application event system (macOS + Linux parity with existing Windows power events), and forwards them into two new common events so apps can subscribe once across OSes. It also includes a broad CLI terminal-output refactor replacing pterm with a new internal/term package (ANSI + glyph spinner), updating multiple commands accordingly.
Changes:
- Add new platform events for macOS (NSWorkspace sleep/wake + screen sleep/wake) and Linux (logind
PrepareForSleep), plus new common eventsCommon.SystemWillSleep/Common.SystemDidWake. - Forward platform-specific power events into the new common events on macOS/Linux/Windows.
- Replace CLI output/spinner plumbing (
pterm) withinternal/termand update multiple CLI commands + dependencies.
Reviewed changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| v3/pkg/events/known_events.go | Registers new common/linux/mac sleep/wake event names as “known events”. |
| v3/pkg/events/events.txt | Declares new sleep/wake events for generator input (including “!” ignored-selector markers). |
| v3/pkg/events/events.go | Adds new events.Common + platform event constants; updates numeric IDs and JS name mapping. |
| v3/pkg/events/events_linux.h | Updates Linux C header event IDs + MAX_EVENTS for newly inserted events. |
| v3/pkg/events/events_ios.h | Renumbers iOS C header event IDs due to earlier insertions in the global event list. |
| v3/pkg/events/events_darwin.h | Updates macOS C header event IDs + adds new workspace sleep/wake event IDs. |
| v3/pkg/application/events_common_windows.go | Forwards Windows APM suspend/resume into new common sleep/wake events. |
| v3/pkg/application/events_common_linux.go | Forwards Linux sleep/wake into common sleep/wake events. |
| v3/pkg/application/events_common_darwin.go | Forwards macOS workspace sleep/wake into common sleep/wake events. |
| v3/pkg/application/application_linux.go | Subscribes to logind PrepareForSleep on system bus and emits Linux sleep/wake events. |
| v3/pkg/application/application_darwin.go | Observes NSWorkspace notification center to receive sleep/wake notifications. |
| v3/pkg/application/application_darwin_delegate.m | Implements NSWorkspace notification selectors and emits corresponding application events. |
| v3/internal/runtime/desktop/@wailsio/runtime/src/event_types.ts | Exposes new event type strings for Common/Linux/Mac in the runtime TS constants. |
| v3/internal/generator/collect/known_events.go | Keeps generator-side known-event list in sync with new sleep/wake events. |
| v3/internal/generator/config/log.go | Removes the pterm-based default logger implementation from generator config. |
| v3/internal/generator/analyse_test.go | Updates test logging to term.Warning (but still references removed logger helper). |
| v3/internal/term/term.go | Introduces new ANSI + glyph terminal UI, spinner, and logger adapter. |
| v3/internal/templates/templates.go | Switches init template UI output from pterm table/confirm to term helpers. |
| v3/internal/doctor/doctor.go | Switches doctor output + spinner from pterm to term helpers and tables. |
| v3/internal/commands/update_cli.go | Switches update output to term + spinner and adjusts messaging/formatting. |
| v3/internal/commands/signing_setup.go | Switches signing setup CLI output from pterm to term. |
| v3/internal/commands/sign.go | Switches sign command output from pterm to term. |
| v3/internal/commands/init.go | Switches init template listing output to term table helper. |
| v3/internal/commands/entitlements_setup.go | Switches entitlements setup output from pterm to term. |
| v3/internal/commands/capabilities.go | Prints JSON via fmt.Println instead of pterm. |
| v3/internal/commands/appimage.go | Replaces pterm progress output with term spinner/info output. |
| v3/cmd/wails3/main.go | Switches top-level CLI error/footer output to term/fmt and adds new footer formatting. |
| v3/go.mod | Removes pterm, adds glyph, updates Go version directive and indirect deps. |
Comments suppressed due to low confidence (1)
v3/internal/generator/analyse_test.go:115
config.DefaultPtermLoggerwas removed frominternal/generator/config/log.go, but this test still calls it. This will fail to compile. Useconfig.NullLogger(or another test logger) when callingFindServicesinstead of the deleted helper.
}
got := make([]string, 0)
services, registerService, err := FindServices(pkgs, systemPaths, config.DefaultPtermLogger(nil))
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| fmt.Println() | ||
| term.HeaderTable(rows) | ||
| fmt.Println() |
| info, err := operatingsystem.Info() | ||
| if err != nil { | ||
| term.StopSpinner(spinner) | ||
| term.Error("Failed to get system information") | ||
| return err |
| msg := text | ||
| frame := 0 | ||
|
|
||
| // ANSI escape code to reset text formatting | ||
| resetFormat := "\x1b[0m" | ||
| app := glyph.NewInlineApp() | ||
| app.Height(1) | ||
| app.SetView( | ||
| glyph.HBox( | ||
| glyph.SpaceW(2), | ||
| glyph.Spinner(&frame).Frames(glyph.SpinnerBraille).FG(glyph.Cyan), |
| func printFooter() { | ||
| if !commands.DisableFooter { | ||
| docsLink := term.Hyperlink("https://v3.wails.io/getting-started/your-first-app/", "wails3 docs") | ||
|
|
||
| pterm.Println(pterm.LightGreen("\nNeed documentation? Run: ") + pterm.LightBlue(docsLink)) | ||
| // Check if we're in a teminal | ||
| printer := pterm.PrefixPrinter{ | ||
| MessageStyle: pterm.NewStyle(pterm.FgLightGreen), | ||
| Prefix: pterm.Prefix{ | ||
| Style: pterm.NewStyle(pterm.FgRed, pterm.BgLightWhite), | ||
| Text: "♥ ", | ||
| }, | ||
| } | ||
|
|
||
| linkText := term.Hyperlink("https://github.com/sponsors/leaanthony", "wails3 sponsor") | ||
| printer.Println("If Wails is useful to you or your company, please consider sponsoring the project: " + pterm.LightBlue(linkText)) | ||
| docsLink := term.Hyperlink("https://v3.wails.io/getting-started/your-first-app/", "docs") | ||
| sponsorLink := term.Hyperlink("https://github.com/sponsors/leaanthony", "github.com/sponsors/leaanthony") | ||
| fmt.Println() | ||
| fmt.Println(" " + term.Dim("docs")+" "+docsLink) | ||
| fmt.Println(" " + term.Red("♥") + " " + sponsorLink) | ||
| fmt.Println() |
| func confirmRemote(_ *Template) bool { | ||
| term.Warning("Remote template — the Wails project takes no responsibility for 3rd party templates.") | ||
| term.Warning("Only use remote templates that you trust.") | ||
| fmt.Println() |
Mirrors the windows:APMSuspend / APMResumeAutomatic / APMResumeSuspend events that Wails v3 already raises from WM_POWERBROADCAST on Windows. NSWorkspace power notifications post to its own notification center (not the default NSNotificationCenter that NSApplicationDelegate uses) so the events are declared with the `!` suffix in events.txt to skip the delegate-selector auto-generation, and observers are wired by hand in application_darwin.go's init() alongside the existing theme-change observer on NSDistributedNotificationCenter. New events: - mac:ApplicationWillSleep (NSWorkspaceWillSleepNotification) - mac:ApplicationDidWake (NSWorkspaceDidWakeNotification) - mac:ApplicationScreensDidSleep (NSWorkspaceScreensDidSleepNotification) - mac:ApplicationScreensDidWake (NSWorkspaceScreensDidWakeNotification) Verification: compile + vet + existing unit tests pass on macOS, linux, windows. Runtime firing of the observers is not exercised by automated tests (same gap as the rest of the AppDelegate selectors) — to be confirmed via xbar2's #807 follow-up wiring and a manual sleep test. Motivation: xbar2 needs DidWake to force a plugin refresh sweep after sleep, addressing upstream xbar issue #807 (the most-upvoted open bug).
Adds Linux equivalents to the mac:ApplicationDidWake / WillSleep events introduced in the previous commit, completing power-event coverage across macOS, Windows, and Linux. Implementation subscribes to org.freedesktop.login1.Manager's PrepareForSleep signal on the system bus. The signal fires twice with a boolean arg — true just before suspend (→ linux:SystemWillSleep), false on resume (→ linux:SystemDidWake). The pattern mirrors monitorThemeChanges() in the same file (goroutine + AddMatchSignal + range over Signal channel), differing only in bus (system vs session) and dispatch logic. Graceful degradation: distros without logind/elogind reachable on the system bus (Alpine, Void, some Devuan setups) log a warning and the goroutine exits — the rest of the application keeps running. This matches the existing theme-monitor behaviour for missing session bus. New events: - linux:SystemWillSleep (PrepareForSleep arg=true) - linux:SystemDidWake (PrepareForSleep arg=false) Verification: darwin build + vet + tests pass. Linux cross-compile requires a GTK toolchain not available in this environment, so runtime behaviour will be confirmed when the branch is built on Linux + a manual systemctl suspend test. The function structure is a near-copy of the existing monitorThemeChanges() so the parse-time risk is low. godbus API calls (ConnectSystemBus, WithMatchInterface, WithMatchMember, WithMatchObjectPath) verified against v5.2.2 (the version in go.mod).
Adds two cross-platform power events so apps can listen for sleep/wake
once instead of branching on OS:
app.OnApplicationEvent(events.Common.SystemDidWake, …)
Each platform's events_common_<os>.go map gains an entry that
forwards the platform-specific event into the common one (same
pattern as Common.ThemeChanged and Common.ApplicationStarted):
darwin: Mac.ApplicationWillSleep → Common.SystemWillSleep
Mac.ApplicationDidWake → Common.SystemDidWake
windows: Windows.APMSuspend → Common.SystemWillSleep
Windows.APMResumeAutomatic → Common.SystemDidWake
linux: Linux.SystemWillSleep → Common.SystemWillSleep
Linux.SystemDidWake → Common.SystemDidWake
Notes:
- Windows APMResumeSuspend is deliberately not mapped — it fires
*after* APMResumeAutomatic when the wake was triggered by user
input, so mapping both would double-fire Common.SystemDidWake.
- Mac.ApplicationScreensDidWake / ScreensDidSleep are kept platform-
specific; they fire on display power state (separate from system
sleep) and have no Linux/Windows equivalent.
Verified: darwin build + tests pass locally; Linux build + tests
pass on lin-node1; windows cross-vet of pkg/events clean.
|
Rebased onto current The PR diff is now 14 files / +787 / −632, all on-topic for the sleep/wake event work. The CodeRabbit can take another pass. |
|
Superseded by #5425 — fresh PR on a branch cleanly rebased onto current master, with documentation added. Closing this thread to avoid review confusion from the earlier sandbox-drift state. |
Summary
Adds cross-platform sleep/wake events so apps can react to the system suspending/resuming. Closes a long-standing gap: Windows already exposed
WM_POWERBROADCASTasWindows.APMSuspend/APMResumeAutomatic/APMResumeSuspend, but macOS and Linux had no equivalent.Three layered additions:
macOS — observes
NSWorkspace's notification centre (not the default one, which is whatNSApplicationDelegateuses) and emits four new events:Mac.ApplicationWillSleep→NSWorkspaceWillSleepNotificationMac.ApplicationDidWake→NSWorkspaceDidWakeNotificationMac.ApplicationScreensDidSleep→NSWorkspaceScreensDidSleepNotificationMac.ApplicationScreensDidWake→NSWorkspaceScreensDidWakeNotificationWired in
application_darwin.go::init()alongside the existing theme-change observer. Events declared with the!suffix inevents.txtso the generator skips its auto-selector emission — NSWorkspace selectors don't belong onNSApplicationDelegate.Linux — subscribes to
org.freedesktop.login1.Manager.PrepareForSleepon the system bus. The signal fires twice with a boolean arg (true before suspend, false on resume), dispatched toLinux.SystemWillSleepandLinux.SystemDidWake. MirrorsmonitorThemeChanges()in shape. Logs a warning + exits gracefully on systems without logind/elogind (Alpine, Void, some Devuan setups) so the rest of the app keeps running.Common forwarding — two new common events:
Common.SystemWillSleepCommon.SystemDidWakeEach platform's
events_common_<os>.gomap gains an entry that forwards the platform-specific event into the common one (same pattern asCommon.ThemeChangedandCommon.ApplicationStarted):Mac.ApplicationWillSleepCommon.SystemWillSleepMac.ApplicationDidWakeCommon.SystemDidWakeWindows.APMSuspendCommon.SystemWillSleepWindows.APMResumeAutomaticCommon.SystemDidWakeLinux.SystemWillSleepCommon.SystemWillSleepLinux.SystemDidWakeCommon.SystemDidWakeApps can now subscribe once, cross-platform:
Design notes
Windows.APMResumeSuspenddeliberately NOT mapped toCommon.SystemDidWake. It fires afterAPMResumeAutomaticwhen the resume was triggered by user input, so mapping both would double-fire the common event for keyboard/mouse wakes.Mac.ApplicationScreensDidSleep/Wakekept platform-specific. They fire on display-power transitions (e.g. lid lowered, display dimmed) distinct from full system sleep. No clean Windows/Linux equivalent — display power lives in DE-specific D-Bus interfaces (GNOME ScreenSaver, KDE PowerManagement) which are too fragmented to unify cleanly.monitorThemeChangesuses —logindis a system-level service.Test plan
go build,go vet,go test ./pkg/events/ ./pkg/application/— all passgo build,go test ./pkg/application/— all passGOOS=windows go vet ./pkg/events/clean (CGO build path can't run here, butevents_common_windows.gois straight Go, mirroring the existing pattern)Common.SystemDidWakefire afterpmset sleepnow+ wakeCommon.SystemDidWakefire aftersystemctl suspend+ wakeCommon.SystemDidWakefire after standby + wakeAutomated tests don't cover any of the existing AppDelegate-selector or D-Bus paths either; the manual sleep test mirrors how the existing theme-change and Windows APM events are validated.
Summary by CodeRabbit