通知
显示带操作按钮和文本输入的原生系统通知
简介
Wails 为桌面应用程序提供了完善的跨平台通知系统。此服务可用于显示原生系统通知,并支持:
- 包含标题、副标题和正文的基本通知
- 带操作按钮和文本回复的交互式通知
- 可复用于操作的通知类别
- 自定义声音(默认、静音或指定名称)
- 附件(所有平台均支持图像;macOS 还支持音频和视频)
- 使用
ThreadID对相关通知进行分组 - 通过
InterruptionLevel设置优先级(passive/active/timeSensitive/critical) - 定时发送(macOS 使用原生机制;Windows 和 Linux 使用进程内定时器)
- 按 ID更新仍处于活动状态的通知
当平台无法支持新增的可选字段时,每个字段都会平稳降级;有关各项功能的平台支持矩阵,请参阅平台注意事项。
基本用法
创建服务
首先,初始化通知服务:
import "github.com/wailsapp/wails/v3/pkg/application"
import "github.com/wailsapp/wails/v3/pkg/services/notifications"
// Create a new notification service
notifier := notifications.New()
//Register the service with the application
app := application.New(application.Options{
Services: []application.Service{
application.NewService(notifier),
},
})通知授权
macOS 上的通知需要用户授权。请求并检查授权:
authorized, err := notifier.CheckNotificationAuthorization()
if err != nil {
// Handle authorization error
}
if authorized {
// Send notifications
} else {
// Request authorization
authorized, err = notifier.RequestNotificationAuthorization()
}在 Windows 和 Linux 上,此方法始终返回true。
通知类型
基本通知
向用户发送一条基本通知,其中包含唯一 ID、标题、可选副标题(macOS 和 Linux)以及正文文本:
notifier.SendNotification(notifications.NotificationOptions{
ID: "unique-id",
Title: "New Calendar Invite",
Subtitle: "From: Jane Doe", // Optional
Body: "Tap to view the event",
})
交互式通知
发送带操作按钮和文本输入框的通知。必须先注册通知类别,才能使用此类通知:
// Define a unique category id
categoryID := "unique-category-id"
// Define a category with actions
category := notifications.NotificationCategory{
ID: categoryID,
Actions: []notifications.NotificationAction{
{
ID: "OPEN",
Title: "Open",
},
{
ID: "ARCHIVE",
Title: "Archive",
Destructive: true, /* macOS-specific */
},
},
HasReplyField: true,
ReplyPlaceholder: "message...",
ReplyButtonTitle: "Reply",
}
// Register the category
notifier.RegisterNotificationCategory(category)
// Send an interactive notification with the actions registered in the provided category
notifier.SendNotificationWithActions(notifications.NotificationOptions{
ID: "unique-id",
Title: "New Message",
Subtitle: "From: Jane Doe",
Body: "Are you able to make it?",
CategoryID: categoryID,
})通知响应
处理用户与通知的交互:
notifier.OnNotificationResponse(func(result notifications.NotificationResult) {
response := result.Response
fmt.Printf("Notification %s was actioned with: %s\n", response.ID, response.ActionIdentifier)
if response.ActionIdentifier == "TEXT_REPLY" {
fmt.Printf("User replied: %s\n", response.UserText)
}
if data, ok := response.UserInfo["sender"].(string); ok {
fmt.Printf("Original sender: %s\n", data)
}
// Emit an event to the frontend
app.Event.Emit("notification", result.Response)
})自定义通知
自定义元数据
基本通知和交互式通知可以包含自定义数据:
notifier.SendNotification(notifications.NotificationOptions{
ID: "unique-id",
Title: "New Calendar Invite",
Subtitle: "From: Jane Doe", // Optional
Body: "Tap to view the event",
Data: map[string]interface{}{
"sender": "jane.doe@example.com",
"timestamp": "2025-03-10T15:30:00Z",
}
})自定义声音
使用Sound控制通知送达时播放的声音。将其保留为nil会播放平台默认声音;设为Silent: true会禁止播放声音;设置Name字段的值可播放指定名称的声音或应用随附的声音。
// Silent
notifier.SendNotification(notifications.NotificationOptions{
ID: "silent-id",
Title: "Background sync complete",
Sound: ¬ifications.NotificationSound{Silent: true},
})
// Named sound
notifier.SendNotification(notifications.NotificationOptions{
ID: "named-id",
Title: "New message",
Body: "Tap to read",
Sound: ¬ifications.NotificationSound{Name: "Ping"},
})各平台解析Name的方式如下:
- macOS — 将
Name传递给[UNNotificationSound soundNamed:];音频文件必须位于应用程序包的Library/Sounds下。 - Windows — 如果
Name已以ms-winsoundevent:或ms-appx:开头,则直接使用;否则,将其封装到ms-winsoundevent:中,作为内置 Toast 事件名称使用(请参阅 Microsoft 的 Toast<audio>架构文档)。 - Linux — 作为 freedesktop 的
sound-name提示转发;能否播放取决于当前使用的通知守护进程和声音主题。
附件
Attachments用于随通知添加媒体文件。macOS 支持任意媒体类型的多个附件;Windows 和 Linux 仅处理第一个图像类型的附件。
notifier.SendNotification(notifications.NotificationOptions{
ID: "image-id",
Title: "Photo uploaded",
Body: "Tap to view",
Attachments: []notifications.NotificationAttachment{
{
ID: "preview",
Path: "/absolute/path/to/image.png",
// Type hint:
// macOS: UTI like "public.png" / "public.audio" (often inferred)
// Windows: "hero" | "appLogoOverride" | "inline" (default "inline")
// Linux: ignored
Type: "inline",
},
},
})Path必须是文件系统绝对路径。macOS 还接受file:// URL。
附加应用随附的文件
操作系统会在通知送达时从磁盘读取附件,因此Path必须解析为最终用户计算机上的真实文件。对于随应用程序打包的资源(例如使用go:embed嵌入的图标或图像),无法硬编码固定的绝对路径,因为该文件位于二进制文件内部,而不是磁盘上的已知位置。请在启动时将其写入可写目录一次,然后传入该路径:
import (
_ "embed"
"os"
"path/filepath"
)
//go:embed assets/preview.png
var previewPNG []byte
// Materialise the embedded asset to a stable path the OS can read.
previewPath := filepath.Join(os.TempDir(), "myapp-preview.png")
if err := os.WriteFile(previewPath, previewPNG, 0o644); err != nil {
// handle error
}
notifier.SendNotification(notifications.NotificationOptions{
ID: "image-id",
Title: "Photo uploaded",
Body: "Tap to view",
Attachments: []notifications.NotificationAttachment{
{ID: "preview", Path: previewPath, Type: "inline"},
},
})用户提供或下载的文件已经具有真实的磁盘路径,因此无需执行此步骤,可直接将其传给Path。未来版本可能会增加直接传入内存中附件字节的功能。
通知串联与分组
ThreadID将相关通知归为一组,以便操作系统在通知中心/操作中心中将其折叠。
notifier.SendNotification(notifications.NotificationOptions{
ID: "msg-42",
Title: "Jane Doe",
Body: "Are you free for lunch?",
ThreadID: "chat:jane.doe",
})中断级别
InterruptionLevel控制通知优先级。请使用以下导出常量之一:
notifier.SendNotification(notifications.NotificationOptions{
ID: "alert-id",
Title: "Server down",
Body: "Investigate immediately",
InterruptionLevel: notifications.InterruptionLevelTimeSensitive,
})| 常量 | 值 | 含义 |
|---|---|---|
InterruptionLevelPassive |
"passive" |
静默送达;不会点亮屏幕,也不会播放默认声音 |
InterruptionLevelActive |
"active" |
默认级别 |
InterruptionLevelTimeSensitive |
"timeSensitive" |
在允许的情况下突破专注模式/勿扰模式的限制 |
InterruptionLevelCritical |
"critical" |
绕过专注模式和铃声设置;macOS 要求具备“关键警报”授权(缺少该授权时会静默降级) |
各平台的映射方式:
- macOS — 设置
UNNotificationContent.interruptionLevel。critical要求 macOS 12+ 并具备“关键警报”授权。 - Windows — 映射到 Toast 的
<toast scenario="...">属性。 - Linux — 映射到 freedesktop 的
urgency提示。
计划投递
Schedule会推迟投递。请仅设置DelaySeconds(从现在起的秒数)或At(Unix 秒数,UTC)中的一个。
// Deliver in 60 seconds
notifier.SendNotification(notifications.NotificationOptions{
ID: "reminder-id",
Title: "Stand up and stretch",
Schedule: ¬ifications.NotificationSchedule{DelaySeconds: 60},
})
// Deliver at an absolute time
notifier.SendNotification(notifications.NotificationOptions{
ID: "alarm-id",
Title: "Meeting in 5 minutes",
Schedule: ¬ifications.NotificationSchedule{At: time.Now().Add(time.Hour).Unix()},
})更新通知
UpdateNotification会替换具有相同ID的现有通知:
notifier.UpdateNotification(notifications.NotificationOptions{
ID: "download-id",
Title: "Download complete",
Body: "report.pdf is ready",
})各平台的行为:
- macOS —
UNUserNotificationCenter会按标识符自动去重,因此现有通知会原地更新。 - Linux — 使用 D-Bus 的
replaces_id参数替换上一条通知。 - Windows — 当前会将其作为新通知重新投递。真正的原地替换需要上游
wintoast支持tag/group。
平台注意事项
在 macOS 上,通知:
- 需要用户授权
- 要求应用已打包并签名(分发时还须经过公证)
- 使用系统标准的通知外观
- 支持
Subtitle - 支持用户文本输入(回复)
- 支持
Destructive操作选项 - 支持任意媒体类型(图像、音频、视频)的多个
Attachments - 支持使用
ThreadID在通知中心中分组 - 支持
InterruptionLevel的所有值(critical要求具备“关键警报”授权) - 支持可在应用重启后继续保留的原生计划投递
- 按
ID自动对UpdateNotification调用去重 - 自动处理深色/浅色模式
在 Windows 上,通知:
- 通过
wintoast后端使用 Windows 系统 Toast 样式 - 适配 Windows 主题设置
- 支持用户文本输入(回复)
- 支持高 DPI 显示器
- 不支持
Subtitle - 支持单个图像
Attachment,其位置提示可设为hero、appLogoOverride或inline(默认为inline) - 支持使用
ThreadID在操作中心中分组 - 通过 Toast 的
scenario属性支持InterruptionLevel - 通过进程内计时器支持计划投递——如果应用在投递前退出,计划通知将丢失
UpdateNotification当前会将其作为新通知重新投递(真正的原地替换尚待上游wintoast支持tag/group)
在 Linux 上,通知使用 D-Bus 的org.freedesktop.Notifications接口。必须有兼容的通知守护进程正在运行,通知才能正常工作。
在 Linux 上,通知:
- 遵循桌面环境主题
- 根据桌面环境规则确定位置
- 支持
Subtitle(对于不会单独呈现它的守护进程,会将其拼接到正文中) - 不支持用户文本输入(不属于 freedesktop 规范)
- 通过
image-path提示支持单个图像Attachment - 支持
ThreadID(在支持的情况下由守护进程处理) Sound.Name会作为sound-name提示传递;是否播放取决于当前使用的守护进程和声音主题- 将
InterruptionLevel映射到 freedesktop 的urgency提示 - 通过进程内定时器支持定时送达——如果应用在送达前退出,定时通知将会丢失
UpdateNotification使用 D-Bus 的replaces_id参数原位替换上一条通知
最佳实践
- 检查并请求授权:
- macOS 需要用户授权
- 提供清晰简洁的通知:
- 使用描述明确的标题、副标题、文本和操作标题
- 妥善处理通知响应:
- 检查通知响应中的错误
- 为用户操作提供反馈
- 考虑平台惯例:
- 遵循平台特定的通知模式
- 遵循系统设置
- 在 Linux 上处理守护进程依赖:
- 检查
SendNotification返回的错误——缺少守护进程会产生 D-Bus 错误 - 软件包文档或应用 README 应注明需要 freedesktop 通知守护进程
示例
查看此示例:
API 参考
服务管理
| 方法 | 说明 |
|---|---|
New() |
创建新的通知服务 |
通知授权
| 方法 | 说明 |
|---|---|
RequestNotificationAuthorization() |
请求显示通知的权限(macOS) |
CheckNotificationAuthorization() |
检查当前通知授权状态(macOS) |
发送通知
| 方法 | 说明 |
|---|---|
SendNotification(options NotificationOptions) |
发送基本通知 |
SendNotificationWithActions(options NotificationOptions) |
发送包含操作的交互式通知 |
UpdateNotification(options NotificationOptions) |
按ID更新正在处理的通知(请参阅更新通知) |
通知类别
| 方法 | 说明 |
|---|---|
RegisterNotificationCategory(category NotificationCategory) |
注册可复用的通知类别 |
RemoveNotificationCategory(categoryID string) |
移除先前注册的类别 |
管理通知
| 方法 | 说明 |
|---|---|
RemoveAllPendingNotifications() |
移除所有待送达的通知(仅限 macOS 和 Linux) |
RemovePendingNotification(identifier string) |
移除指定的待送达通知(仅限 macOS 和 Linux) |
RemoveAllDeliveredNotifications() |
移除所有已送达的通知(仅限 macOS 和 Linux) |
RemoveDeliveredNotification(identifier string) |
移除指定的已送达通知(仅限 macOS 和 Linux) |
RemoveNotification(identifier string) |
移除通知(Linux 特有) |
事件处理
| 方法 | 说明 |
|---|---|
OnNotificationResponse(callback func(result NotificationResult)) |
为通知响应注册回调函数 |
结构体和类型
NotificationOptions
type NotificationOptions struct {
ID string // Unique identifier for the notification (required)
Title string // Main notification title (required)
Subtitle string // Subtitle text (macOS and Linux only)
Body string // Main notification content
CategoryID string // Category identifier for interactive notifications
Data map[string]interface{} // Custom data to associate with the notification
// Sound controls the sound played on delivery. nil = platform default.
Sound *NotificationSound
// Attachments are media files shown alongside the notification.
// macOS supports multiple attachments of any media type;
// Windows and Linux honour the first image-typed attachment.
Attachments []NotificationAttachment
// ThreadID groups related notifications together in
// Notification Center / Action Center / the Linux notification daemon.
ThreadID string
// InterruptionLevel controls priority. One of "passive",
// "active" (default), "timeSensitive", "critical".
InterruptionLevel string
// Schedule defers delivery. macOS uses a native trigger that survives
// restarts; Windows and Linux use an in-process timer that does NOT.
Schedule *NotificationSchedule
}NotificationSound
type NotificationSound struct {
Silent bool // If true, no sound is played
Name string // Named/bundled sound (see "Custom Sound" above)
}NotificationAttachment
type NotificationAttachment struct {
ID string // Optional identifier
Path string // Absolute filesystem path (macOS also accepts file:// URLs)
// Type is an optional placement/UTI hint:
// macOS: UTI like "public.png" / "public.audio" (often inferred)
// Windows: "hero" | "appLogoOverride" | "inline" (default "inline")
// Linux: ignored (always image-path hint)
Type string
}NotificationSchedule
// Exactly one of DelaySeconds or At must be set. At is Unix seconds (UTC).
type NotificationSchedule struct {
DelaySeconds int // Seconds from now until delivery
At int64 // Absolute Unix timestamp (seconds, UTC)
}InterruptionLevel 常量
const (
InterruptionLevelPassive = "passive"
InterruptionLevelActive = "active" // default
InterruptionLevelTimeSensitive = "timeSensitive"
InterruptionLevelCritical = "critical"
)NotificationCategory
type NotificationCategory struct {
ID string // Unique identifier for the category
Actions []NotificationAction // Button actions for the notification
HasReplyField bool // Whether to include a text input field
ReplyPlaceholder string // Placeholder text for the input field
ReplyButtonTitle string // Text for the reply button
}NotificationAction
type NotificationAction struct {
ID string // Unique identifier for the action
Title string // Button text
Destructive bool // Whether the action is destructive (macOS-specific)
}NotificationResponse
type NotificationResponse struct {
ID string // Notification identifier
ActionIdentifier string // Action that was triggered
CategoryID string // Category of the notification
Title string // Title of the notification
Subtitle string // Subtitle of the notification
Body string // Body text of the notification
UserText string // Text entered by the user
UserInfo map[string]interface{} // Custom data from the notification
}NotificationResult
type NotificationResult struct {
Response NotificationResponse // Response data
Error error // Any error that occurred
}