From b830a45333bdf3b0898d517159764f2e04f2179c Mon Sep 17 00:00:00 2001 From: Zoltan Papp Date: Sun, 31 May 2026 01:34:56 +0200 Subject: [PATCH] ui: request macOS notification authorization on startup Without an explicit authorization request the macOS notification center keeps the app at .notDetermined and silently drops every toast. Request it from the ApplicationStarted hook (after the notifier's Startup has initialised the delegate), off the main goroutine since the call blocks until the user responds. Linux/Windows notifier stubs report authorized, so this is a no-op there. --- client/ui/main.go | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/client/ui/main.go b/client/ui/main.go index cc8c9bbd9..5a119c0c1 100644 --- a/client/ui/main.go +++ b/client/ui/main.go @@ -11,6 +11,7 @@ import ( "runtime" "strings" + "github.com/sirupsen/logrus" "github.com/wailsapp/wails/v3/pkg/application" "github.com/wailsapp/wails/v3/pkg/events" "github.com/wailsapp/wails/v3/pkg/services/notifications" @@ -104,6 +105,13 @@ func main() { update := services.NewUpdate(conn, updaterHolder) daemonFeed := services.NewDaemonFeed(conn, app.Event, updaterHolder) notifier := notifications.New() + // macOS won't surface any toast until the app has requested permission; + // the request runs after ApplicationStarted so the notifier's Startup has + // initialised the notification-center delegate. Linux/Windows stubs return + // authorized, so this is a no-op there. + app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(*application.ApplicationEvent) { + go requestNotificationAuthorization(notifier) + }) bundle, prefStore, localizer := buildI18n(app) @@ -173,6 +181,25 @@ func main() { } } +// requestNotificationAuthorization prompts for macOS notification permission +// when the app first runs unauthorized. RequestNotificationAuthorization +// blocks until the user responds (up to 3 minutes on macOS), so callers run +// it in a goroutine. On Linux/Windows the Wails notifier stubs report +// authorized, making this a no-op. +func requestNotificationAuthorization(notifier *notifications.NotificationService) { + authorized, err := notifier.CheckNotificationAuthorization() + if err != nil { + logrus.Debugf("check notification authorization: %v", err) + return + } + if authorized { + return + } + if _, err := notifier.RequestNotificationAuthorization(); err != nil { + logrus.Debugf("request notification authorization: %v", err) + } +} + // parseFlagsAndInitLog parses the CLI flags, initialises the logger, and // returns the resolved daemon gRPC address. func parseFlagsAndInitLog() string { @@ -294,8 +321,8 @@ func newMainWindow(app *application.App, prefStore *preferences.Store) *applicat DisableResize: true, MinimiseButtonState: application.ButtonHidden, MaximiseButtonState: application.ButtonHidden, - Mac: services.AppleMacOSAppearanceOptions(), - Windows: services.MicrosoftWindowsAppearanceOptions(), + Mac: services.AppleMacOSAppearanceOptions(), + Windows: services.MicrosoftWindowsAppearanceOptions(), Linux: application.LinuxWindow{ Icon: iconWindow, },