Move remote deployment to a vendored source bundle built on the target host via Docker so redeploys no longer require local cross-compilation or host Go installation. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
234 lines
7.6 KiB
Go
234 lines
7.6 KiB
Go
// Package toast wraps the lower-level wintoast api and provides an easy way
|
|
// to send and respond to toast notifications on Windows.
|
|
//
|
|
// First, setup your AppData vis SetAppData function. This will install your
|
|
// application metadata into the Windows Registry.
|
|
//
|
|
// Then, if you want in-process callback to be invoked upon user interaction,
|
|
// invoke SetActivationCallback.
|
|
//
|
|
// Finally, generate your notification by instantiation a toast.Notification
|
|
// and pushing it with Push method.
|
|
package toast
|
|
|
|
import (
|
|
"bytes"
|
|
|
|
"git.sr.ht/~jackmordaunt/go-toast/v2/tmpl"
|
|
"git.sr.ht/~jackmordaunt/go-toast/v2/wintoast"
|
|
)
|
|
|
|
// Notification
|
|
//
|
|
// The toast notification data. The following fields are strongly recommended;
|
|
// - AppID
|
|
// - Title
|
|
//
|
|
// If no toastAudio is provided, then the toast notification will be silent.
|
|
//
|
|
// The AppID is shown beneath the toast message (in certain cases), and above the notification within the Action
|
|
// Center - and is used to group your notifications together. It is recommended that you provide a "pretty"
|
|
// name for your app, and not something like "com.example.MyApp". It can be ellided if the value has already
|
|
// been set via SetAppData.
|
|
//
|
|
// If no Title is provided, but a Body is, the body will display as the toast notification's title -
|
|
// which is a slightly different font style (heavier).
|
|
//
|
|
// The Icon should be an absolute path to the icon (as the toast is invoked from a temporary path on the user's
|
|
// system, not the working directory).
|
|
//
|
|
// If you would like the toast to call an external process/open a webpage, then you can set ActivationArguments
|
|
// to the uri you would like to trigger when the toast is clicked. For example: "https://google.com" would open
|
|
// the Google homepage when the user clicks the toast notification.
|
|
// By default, clicking the toast just hides/dismisses it.
|
|
//
|
|
// The following would show a notification to the user letting them know they received an email, and opens
|
|
// gmail.com when they click the notification. It also makes the Windows 10 "mail" sound effect.
|
|
//
|
|
// toast := toast.Notification{
|
|
// AppID: "Google Mail",
|
|
// Title: email.Subject,
|
|
// Message: email.Preview,
|
|
// Icon: "C:/Program Files/Google Mail/icons/logo.png",
|
|
// ActivationArguments: "https://gmail.com",
|
|
// Audio: toast.Mail,
|
|
// }
|
|
//
|
|
// err := toast.Push()
|
|
type Notification struct {
|
|
// The name of your app. This value shows up in Windows Action Centre, so make it
|
|
// something readable for your users.
|
|
AppID string
|
|
|
|
// The main title/heading for the toast notification.
|
|
Title string
|
|
|
|
// The single/multi line message to display for the toast notification.
|
|
Body string
|
|
|
|
// An optional path to an image on the OS to display to the left of the title & message.
|
|
Icon string
|
|
|
|
// An optional crop style for the Icon.
|
|
IconCrop CropStyle
|
|
|
|
// An optional path to an image to display as a bold hero image.
|
|
HeroIcon string
|
|
|
|
// A color to show as the icon background.
|
|
IconBackgroundColor string
|
|
|
|
// Action to take when the notification is as a whole activated.
|
|
ActivationType ActivationType
|
|
|
|
// The activation/action arguments (invoked when the user clicks the notification).
|
|
// This is returned to the callback when activated.
|
|
ActivationArguments string
|
|
|
|
// Optional text input to display before the actions.
|
|
Inputs []Input
|
|
|
|
// Optional action buttons to display below the notification title & message.
|
|
Actions []Action
|
|
|
|
// The audio to play when displaying the toast
|
|
Audio toastAudio
|
|
|
|
// Whether to loop the audio (default false).
|
|
Loop bool
|
|
|
|
// How long the toast should show up for (short/long).
|
|
Duration toastDuration
|
|
|
|
// This is an absolute path to an executable that will launched by the
|
|
// Windows Runtime when the COM server is not running. This executable must be able
|
|
// to handle the -Embedding flag that Windows invokes it with.
|
|
ActivationExe string
|
|
}
|
|
|
|
// CropStyle specifies the hint-crop attribute for an image.
|
|
type CropStyle = string
|
|
|
|
const (
|
|
CropStyleEmpty CropStyle = ""
|
|
CropStyleSquare CropStyle = "square"
|
|
CropStyleCircle CropStyle = "circle"
|
|
)
|
|
|
|
// UserData contains user supplied data from the notification, such as text input
|
|
// or a selection.
|
|
type UserData = wintoast.UserData
|
|
|
|
// Input
|
|
//
|
|
// Defines an input element, generally a text input.
|
|
// See https://learn.microsoft.com/en-us/uwp/schemas/tiles/toastschema/element-input for more info.
|
|
//
|
|
// Inputs are by default textual, however if selections are supplied the input will be rendered
|
|
// as a select input.
|
|
type Input struct {
|
|
ID string
|
|
Title string
|
|
Placeholder string
|
|
Selections []InputSelection
|
|
}
|
|
|
|
// InputSelection
|
|
//
|
|
// Defines an input selection for use with select inputs.
|
|
// See https://learn.microsoft.com/en-us/uwp/schemas/tiles/toastschema/element-selection for more info.
|
|
type InputSelection struct {
|
|
ID string
|
|
Content string
|
|
}
|
|
|
|
// Action
|
|
//
|
|
// Defines an actionable button.
|
|
// See https://msdn.microsoft.com/en-us/windows/uwp/controls-and-patterns/tiles-and-notifications-adaptive-interactive-toasts for more info.
|
|
//
|
|
// toast.Action{toast.Protocol, "Open Maps", "bingmaps:?q=sushi"}
|
|
//
|
|
// TODO(jfm): we can likely support an activation callback directly in the Action.
|
|
type Action struct {
|
|
Type ActivationType
|
|
Content string
|
|
Arguments string
|
|
InputID string // optional ID of any related input, affects styling.
|
|
}
|
|
|
|
// Push the notification to the Windows Runtime via the COM API.
|
|
// Ensure [SetAppData] has been called prior to pushing notifications.
|
|
//
|
|
// notification := toast.Notification{
|
|
// AppID: "Example App",
|
|
// Title: "My notification",
|
|
// Message: "Some message about how important something is...",
|
|
// Icon: "go.png",
|
|
// Actions: []toast.Action{
|
|
// {"protocol", "I'm a button", ""},
|
|
// {"protocol", "Me too!", ""},
|
|
// },
|
|
// }
|
|
// err := notification.Push()
|
|
// if err != nil {
|
|
// log.Fatalln(err)
|
|
// }
|
|
func (n *Notification) Push() error {
|
|
n.applyDefaults()
|
|
xml, err := n.buildXML()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return wintoast.Push(n.AppID, xml, wintoast.PowershellFallback)
|
|
}
|
|
|
|
func (n *Notification) applyDefaults() {
|
|
if n.ActivationType == "" {
|
|
n.ActivationType = Foreground
|
|
}
|
|
if n.Duration == "" {
|
|
n.Duration = Short
|
|
}
|
|
if n.Audio == "" {
|
|
n.Audio = Default
|
|
}
|
|
}
|
|
|
|
func (n *Notification) buildXML() (string, error) {
|
|
var out bytes.Buffer
|
|
err := tmpl.XMLTemplate.Execute(&out, n)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return out.String(), nil
|
|
}
|
|
|
|
// SetActivationCallback sets the global activation callback.
|
|
//
|
|
// The first argument contains application defined data (embedded within the xml),
|
|
// which is how the callback knows which part of the toast was activated.
|
|
// Argument data is defined by `toast.Action.Arguments` on the notification.
|
|
//
|
|
// The second argument contains user defined data (input/selected by user).
|
|
// All elements of user input will be supplied here, even if the value is empty.
|
|
// User inputs correspond to all `toast.Input`s defined on the notification.
|
|
//
|
|
// This function will be invoked when a toast notification is interacted with.
|
|
//
|
|
// This will do nothing if the the powershell fallback is in-effect.
|
|
func SetActivationCallback(cb func(args string, data []UserData)) {
|
|
wintoast.SetActivationCallback(func(appUserModelId, invokedArgs string, userData []wintoast.UserData) {
|
|
cb(invokedArgs, userData)
|
|
})
|
|
}
|
|
|
|
type AppData = wintoast.AppData
|
|
|
|
// SetAppData sets application metadata in the Windows Registry.
|
|
// This is required to display the application name, as well as any branding.
|
|
// Registry is global state, hence it makes sense to set it global.
|
|
func SetAppData(data AppData) error {
|
|
return wintoast.SetAppData(data)
|
|
}
|