Skip to content

[v3] Notification Service bugs (Windows & Linux) #4449

@popaprozac

Description

@popaprozac

Description

Current issues:

macOS Windows Linux
No issues
  • Notification service (send) methods called in main fail—ServiceStartup is required before service usage
  • Basic notification missing user supplied Data (empty or not) will fail to decode the notification payload when actioned
  • Notification service (send) methods called in main will cause a panic—ServiceStartup is required before service usage

Thoughts on the shared Windows and Linux issue about ServiceStartup timing would be appreciated. I'll look at other services but what is a pattern we like here. Otherwise we need to document the timing issue. It is unlikely to send a notification in main before calling app.Run().

In the effort to add this to v2, we could add a similar InitializeNotifications to v3 or detail in documentation the need to use the notification service in an ApplicationStarted event.

Calling ServiceStartup on the service from main resolves the issue.


macOS: no need for any coordination/functionality bootstrapped in `ServiceStartup`.
notifications_darwin.go
func (dn *darwinNotifier) Startup(ctx context.Context, options application.ServiceOptions) error {
  if !isNotificationAvailable() {
  	return fmt.Errorf("notifications are not available on this system")
  }
  if !checkBundleIdentifier() {
  	return fmt.Errorf("notifications require a valid bundle identifier")
  }
  if !bool(C.ensureDelegateInitialized()) {
  	return fmt.Errorf("failed to initialize notification center delegate")
  }
  return nil
}

Windows: grabs the app icon for use in the toast, ensures Registry entries are populated, and the toast callback handler is hooked up.
notifications_window.go
func (wn *windowsNotifier) Startup(ctx context.Context, options application.ServiceOptions) error {
  wn.categoriesLock.Lock()
  defer wn.categoriesLock.Unlock()

  app := application.Get()
  cfg := app.Config()

  wn.appName = cfg.Name

  guid, err := wn.getGUID()
  if err != nil {
  	return err
  }
  wn.appGUID = guid

  wn.iconPath = filepath.Join(os.TempDir(), wn.appName+wn.appGUID+".png")

  exe, err := os.Executable()
  if err != nil {
  	return fmt.Errorf("failed to get executable path: %w", err)
  }
  wn.exePath = exe

  // Create the registry key for the toast activator
  key, _, err := registry.CreateKey(registry.CURRENT_USER,
  	`Software\Classes\CLSID\`+wn.appGUID+`\LocalServer32`, registry.ALL_ACCESS)
  if err != nil {
  	return fmt.Errorf("failed to create CLSID key: %w", err)
  }

  if err := key.SetStringValue("", fmt.Sprintf("\"%s\" %%1", wn.exePath)); err != nil {
  	return fmt.Errorf("failed to set CLSID server path: %w", err)
  }
  key.Close()

  toast.SetAppData(toast.AppData{
  	AppID:         wn.appName,
  	GUID:          guid,
  	IconPath:      wn.iconPath,
  	ActivationExe: wn.exePath,
  })

  toast.SetActivationCallback(func(args string, data []toast.UserData) {
  	result := NotificationResult{}

  	actionIdentifier, options, err := parseNotificationResponse(args)

  	if err != nil {
  		result.Error = err
  	} else {
  		// Subtitle is retained but was not shown with the notification
  		response := NotificationResponse{
  			ID:               options.ID,
  			ActionIdentifier: actionIdentifier,
  			Title:            options.Title,
  			Subtitle:         options.Subtitle,
  			Body:             options.Body,
  			CategoryID:       options.CategoryID,
  			UserInfo:         options.Data,
  		}

  		if userText, found := wn.getUserText(data); found {
  			response.UserText = userText
  		}

  		result.Response = response
  	}

  	if ns := getNotificationService(); ns != nil {
  		ns.handleNotificationResult(result)
  	}
  })

  // Register the class factory for the toast activator
  if err := registerFactoryInternal(wintoast.ClassFactory); err != nil {
  	return fmt.Errorf("CoRegisterClassObject failed: %w", err)
  }

  return wn.loadCategoriesFromRegistry()
}

Linux: creates the dbus connection and sets up the dbus signal handlers.
notifications_linux.go
func (ln *linuxNotifier) Startup(ctx context.Context, options application.ServiceOptions) error {
  ln.appName = application.Get().Config().Name

  conn, err := dbus.ConnectSessionBus()
  if err != nil {
  	return fmt.Errorf("failed to connect to session bus: %w", err)
  }
  ln.conn = conn

  if err := ln.loadCategories(); err != nil {
  	fmt.Printf("Failed to load notification categories: %v\n", err)
  }

  var signalCtx context.Context
  signalCtx, ln.cancel = context.WithCancel(context.Background())

  if err := ln.setupSignalHandling(signalCtx); err != nil {
  	return fmt.Errorf("failed to set up notification signal handling: %w", err)
  }

  return nil
}

To Reproduce

On Windows or Linux, in main:

  1. Create a notification service
  2. Call SendNotification
Windows Linux
Error saving icon: open : The system cannot find the file specified. panic: runtime error: invalid memory address or nil pointer dereference

On Windows, from Go or JS:

  1. Call SendNotification or SendNotificationWithActions without Data defined

Warning: Failed to decode notification response: failed to decode base64 payload: illegal base64 data at input byte 7

Expected behaviour

Expect the notification service to be usable right after being created.
On Windows, we should be able to send notifications without included arbitrary user data.

Screenshots

No response

Attempted Fixes

No response

System Details

Wails (v3.0.0-dev)  Wails Doctor

# System

┌──────────────────────────────────────────────────┐
| Name          | MacOS                            |
| Version       | 26.0                             |
| ID            | 25A5316i                         |
| Branding      | MacOS 26.0                       |
| Platform      | darwin                           |
| Architecture  | arm64                            |
| Apple Silicon | true                             |
| CPU           | Apple M4 Max                     |
| CPU 1         | Apple M4 Max                     |
| CPU 2         | Apple M4 Max                     |
| GPU           | 32 cores, Metal Support: Metal 4 |
| Memory        | 36 GB                            |
└──────────────────────────────────────────────────┘

# Build Environment

┌─────────────────────────────────────────────────────────┐
| Wails CLI    | v3.0.0-dev                               |
| Go Version   | go1.24.1                                 |
| Revision     | 67058c092378d46b678e5758fa476742ea5ba9aa |
| Modified     | true                                     |
| -buildmode   | exe                                      |
| -compiler    | gc                                       |
| CGO_CFLAGS   |                                          |
| CGO_CPPFLAGS |                                          |
| CGO_CXXFLAGS |                                          |
| CGO_ENABLED  | 1                                        |
| CGO_LDFLAGS  |                                          |
| GOARCH       | arm64                                    |
| GOARM64      | v8.0                                     |
| GOOS         | darwin                                   |
| vcs          | git                                      |
| vcs.modified | true                                     |
| vcs.revision | 67058c092378d46b678e5758fa476742ea5ba9aa |
| vcs.time     | 2025-07-25T12:03:26Z                     |
└─────────────────────────────────────────────────────────┘

# Dependencies

┌────────────────────────────────────────────────────────────────────────┐
| Xcode cli tools | 2410                                                 |
| npm             | 10.9.2                                               |
| *NSIS           | Not Installed. Install with `brew install makensis`. |
|                                                                        |
└─────────────────────── * - Optional Dependency ────────────────────────┘

# Checking for issues

 SUCCESS  No issues found

# Diagnosis

 SUCCESS  Your system is ready for Wails development!

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions