toast.go (7764B)
1 // Package toast wraps the lower-level wintoast api and provides an easy way 2 // to send and respond to toast notifications on Windows. 3 // 4 // First, setup your AppData vis SetAppData function. This will install your 5 // application metadata into the Windows Registry. 6 // 7 // Then, if you want in-process callback to be invoked upon user interaction, 8 // invoke SetActivationCallback. 9 // 10 // Finally, generate your notification by instantiation a toast.Notification 11 // and pushing it with Push method. 12 package toast 13 14 import ( 15 "bytes" 16 17 "git.sr.ht/~jackmordaunt/go-toast/v2/tmpl" 18 "git.sr.ht/~jackmordaunt/go-toast/v2/wintoast" 19 ) 20 21 // Notification 22 // 23 // The toast notification data. The following fields are strongly recommended; 24 // - AppID 25 // - Title 26 // 27 // If no toastAudio is provided, then the toast notification will be silent. 28 // 29 // The AppID is shown beneath the toast message (in certain cases), and above the notification within the Action 30 // Center - and is used to group your notifications together. It is recommended that you provide a "pretty" 31 // name for your app, and not something like "com.example.MyApp". It can be ellided if the value has already 32 // been set via SetAppData. 33 // 34 // If no Title is provided, but a Body is, the body will display as the toast notification's title - 35 // which is a slightly different font style (heavier). 36 // 37 // The Icon should be an absolute path to the icon (as the toast is invoked from a temporary path on the user's 38 // system, not the working directory). 39 // 40 // If you would like the toast to call an external process/open a webpage, then you can set ActivationArguments 41 // to the uri you would like to trigger when the toast is clicked. For example: "https://google.com" would open 42 // the Google homepage when the user clicks the toast notification. 43 // By default, clicking the toast just hides/dismisses it. 44 // 45 // The following would show a notification to the user letting them know they received an email, and opens 46 // gmail.com when they click the notification. It also makes the Windows 10 "mail" sound effect. 47 // 48 // toast := toast.Notification{ 49 // AppID: "Google Mail", 50 // Title: email.Subject, 51 // Message: email.Preview, 52 // Icon: "C:/Program Files/Google Mail/icons/logo.png", 53 // ActivationArguments: "https://gmail.com", 54 // Audio: toast.Mail, 55 // } 56 // 57 // err := toast.Push() 58 type Notification struct { 59 // The name of your app. This value shows up in Windows Action Centre, so make it 60 // something readable for your users. 61 AppID string 62 63 // The main title/heading for the toast notification. 64 Title string 65 66 // The single/multi line message to display for the toast notification. 67 Body string 68 69 // An optional path to an image on the OS to display to the left of the title & message. 70 Icon string 71 72 // An optional crop style for the Icon. 73 IconCrop CropStyle 74 75 // An optional path to an image to display as a bold hero image. 76 HeroIcon string 77 78 // A color to show as the icon background. 79 IconBackgroundColor string 80 81 // Action to take when the notification is as a whole activated. 82 ActivationType ActivationType 83 84 // The activation/action arguments (invoked when the user clicks the notification). 85 // This is returned to the callback when activated. 86 ActivationArguments string 87 88 // Optional text input to display before the actions. 89 Inputs []Input 90 91 // Optional action buttons to display below the notification title & message. 92 Actions []Action 93 94 // The audio to play when displaying the toast 95 Audio toastAudio 96 97 // Whether to loop the audio (default false). 98 Loop bool 99 100 // How long the toast should show up for (short/long). 101 Duration toastDuration 102 103 // This is an absolute path to an executable that will launched by the 104 // Windows Runtime when the COM server is not running. This executable must be able 105 // to handle the -Embedding flag that Windows invokes it with. 106 ActivationExe string 107 } 108 109 // CropStyle specifies the hint-crop attribute for an image. 110 type CropStyle = string 111 112 const ( 113 CropStyleEmpty CropStyle = "" 114 CropStyleSquare CropStyle = "square" 115 CropStyleCircle CropStyle = "circle" 116 ) 117 118 // UserData contains user supplied data from the notification, such as text input 119 // or a selection. 120 type UserData = wintoast.UserData 121 122 // Input 123 // 124 // Defines an input element, generally a text input. 125 // See https://learn.microsoft.com/en-us/uwp/schemas/tiles/toastschema/element-input for more info. 126 // 127 // Inputs are by default textual, however if selections are supplied the input will be rendered 128 // as a select input. 129 type Input struct { 130 ID string 131 Title string 132 Placeholder string 133 Selections []InputSelection 134 } 135 136 // InputSelection 137 // 138 // Defines an input selection for use with select inputs. 139 // See https://learn.microsoft.com/en-us/uwp/schemas/tiles/toastschema/element-selection for more info. 140 type InputSelection struct { 141 ID string 142 Content string 143 } 144 145 // Action 146 // 147 // Defines an actionable button. 148 // See https://msdn.microsoft.com/en-us/windows/uwp/controls-and-patterns/tiles-and-notifications-adaptive-interactive-toasts for more info. 149 // 150 // toast.Action{toast.Protocol, "Open Maps", "bingmaps:?q=sushi"} 151 // 152 // TODO(jfm): we can likely support an activation callback directly in the Action. 153 type Action struct { 154 Type ActivationType 155 Content string 156 Arguments string 157 InputID string // optional ID of any related input, affects styling. 158 } 159 160 // Push the notification to the Windows Runtime via the COM API. 161 // Ensure [SetAppData] has been called prior to pushing notifications. 162 // 163 // notification := toast.Notification{ 164 // AppID: "Example App", 165 // Title: "My notification", 166 // Message: "Some message about how important something is...", 167 // Icon: "go.png", 168 // Actions: []toast.Action{ 169 // {"protocol", "I'm a button", ""}, 170 // {"protocol", "Me too!", ""}, 171 // }, 172 // } 173 // err := notification.Push() 174 // if err != nil { 175 // log.Fatalln(err) 176 // } 177 func (n *Notification) Push() error { 178 n.applyDefaults() 179 xml, err := n.buildXML() 180 if err != nil { 181 return err 182 } 183 return wintoast.Push(n.AppID, xml, wintoast.PowershellFallback) 184 } 185 186 func (n *Notification) applyDefaults() { 187 if n.ActivationType == "" { 188 n.ActivationType = Foreground 189 } 190 if n.Duration == "" { 191 n.Duration = Short 192 } 193 if n.Audio == "" { 194 n.Audio = Default 195 } 196 } 197 198 func (n *Notification) buildXML() (string, error) { 199 var out bytes.Buffer 200 err := tmpl.XMLTemplate.Execute(&out, n) 201 if err != nil { 202 return "", err 203 } 204 return out.String(), nil 205 } 206 207 // SetActivationCallback sets the global activation callback. 208 // 209 // The first argument contains application defined data (embedded within the xml), 210 // which is how the callback knows which part of the toast was activated. 211 // Argument data is defined by `toast.Action.Arguments` on the notification. 212 // 213 // The second argument contains user defined data (input/selected by user). 214 // All elements of user input will be supplied here, even if the value is empty. 215 // User inputs correspond to all `toast.Input`s defined on the notification. 216 // 217 // This function will be invoked when a toast notification is interacted with. 218 // 219 // This will do nothing if the the powershell fallback is in-effect. 220 func SetActivationCallback(cb func(args string, data []UserData)) { 221 wintoast.SetActivationCallback(func(appUserModelId, invokedArgs string, userData []wintoast.UserData) { 222 cb(invokedArgs, userData) 223 }) 224 } 225 226 type AppData = wintoast.AppData 227 228 // SetAppData sets application metadata in the Windows Registry. 229 // This is required to display the application name, as well as any branding. 230 // Registry is global state, hence it makes sense to set it global. 231 func SetAppData(data AppData) error { 232 return wintoast.SetAppData(data) 233 }