├── LICENSE ├── README.md ├── dbus.go ├── dbus_test.go ├── doc.go ├── go.mod └── go.sum /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Jason Ayre 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-notify 2 | 3 | [![PkgGoDev](https://pkg.go.dev/badge/github.com/TheCreeper/go-notify)](https://pkg.go.dev/github.com/TheCreeper/go-notify) 4 | 5 | Package notify provides an implementation of the Gnome DBus 6 | [Notifications Specification](https://developer.gnome.org/notification-spec). 7 | 8 | ## Examples 9 | 10 | Display a simple notification. 11 | ```Go 12 | ntf := notify.NewNotification("Test Notification", "Just a test") 13 | if _, err := ntf.Show(); err != nil { 14 | return 15 | } 16 | ``` 17 | 18 | Display a notification with an icon. Consult the 19 | [Icon Naming Specification](http://standards.freedesktop.org/icon-naming-spec). 20 | ```Go 21 | ntf := notify.NewNotification("Test Notification", "Just a test") 22 | ntf.AppIcon = "network-wireless" 23 | if _, err := ntf.Show(); err != nil { 24 | return 25 | } 26 | ``` 27 | 28 | Display a notification that never expires. 29 | ```Go 30 | ntf := notify.NewNotification("Test Notification", "Just a test") 31 | ntf.Timeout = notify.ExpiresNever 32 | if _, err := ntf.Show(); err != nil { 33 | return 34 | } 35 | ``` 36 | 37 | Play a sound with the notification. 38 | ```Go 39 | ntf := notify.NewNotification("Test Notification", "Just a test") 40 | ntf.Hints = make(map[string]interface{}) 41 | ntf.Hints[notify.HintSoundFile] = "/home/my-username/sound.oga" 42 | if _, err := ntf.Show(); err != nil { 43 | return 44 | } 45 | ``` 46 | -------------------------------------------------------------------------------- /dbus.go: -------------------------------------------------------------------------------- 1 | package notify 2 | 3 | import ( 4 | "context" 5 | "github.com/godbus/dbus/v5" 6 | "image" 7 | ) 8 | 9 | // Notification object paths and interfaces. 10 | const ( 11 | DbusObjectPath = "/org/freedesktop/Notifications" 12 | DbusInterfacePath = "org.freedesktop.Notifications" 13 | SignalNotificationClosed = "org.freedesktop.Notifications.NotificationClosed" 14 | SignalActionInvoked = "org.freedesktop.Notifications.ActionInvoked" 15 | CallGetCapabilities = "org.freedesktop.Notifications.GetCapabilities" 16 | CallCloseNotification = "org.freedesktop.Notifications.CloseNotification" 17 | CallNotify = "org.freedesktop.Notifications.Notify" 18 | CallGetServerInformation = "org.freedesktop.Notifications.GetServerInformation" 19 | DbusMemberActionInvoked = "ActionInvoked" 20 | DbusMemberNotificationClosed = "NotificationClosed" 21 | ) 22 | 23 | // Notification expire timeout. 24 | const ( 25 | ExpiresDefault = -1 26 | ExpiresNever = 0 27 | ) 28 | 29 | // Notification Categories 30 | const ( 31 | ClassDevice = "device" 32 | ClassDeviceAdded = "device.added" 33 | ClassDeviceError = "device.error" 34 | ClassDeviceRemoved = "device.removed" 35 | ClassEmail = "email" 36 | ClassEmailArrived = "email.arrived" 37 | ClassEmailBounced = "email.bounced" 38 | ClassIm = "im" 39 | ClassImError = "im.error" 40 | ClassImReceived = "im.received" 41 | ClassNetwork = "network" 42 | ClassNetworkConnected = "network.connected" 43 | ClassNetworkDisconnected = "network.disconnected" 44 | ClassNetworkError = "network.error" 45 | ClassPresence = "presence" 46 | ClassPresenceOffline = "presence.offline" 47 | ClassPresenceOnline = "presence.online" 48 | ClassTransfer = "transfer" 49 | ClassTransferComplete = "transfer.complete" 50 | ClassTransferError = "transfer.error" 51 | ) 52 | 53 | // Urgency Levels 54 | const ( 55 | UrgencyLow = byte(0) 56 | UrgencyNormal = byte(1) 57 | UrgencyCritical = byte(2) 58 | ) 59 | 60 | // Hints 61 | const ( 62 | HintActionIcons = "action-icons" 63 | HintCategory = "category" 64 | HintDesktopEntry = "desktop-entry" 65 | HintImageData = "image-data" 66 | HintImagePath = "image-path" 67 | HintResident = "resident" 68 | HintSoundFile = "sound-file" 69 | HintSoundName = "sound-name" 70 | HintSuppressSound = "suppress-sound" 71 | HintTransient = "transient" 72 | HintX = "x" 73 | HintY = "y" 74 | HintUrgency = "urgency" 75 | ) 76 | 77 | // Capabilities is a struct containing the capabilities of the notification 78 | // server. 79 | type Capabilities struct { 80 | // Supports using icons instead of text for displaying actions. 81 | ActionIcons bool 82 | 83 | // The server will provide any specified actions to the user. 84 | Actions bool 85 | 86 | // Supports body text. Some implementations may only show the summary. 87 | Body bool 88 | 89 | // The server supports hyperlinks in the notifications. 90 | BodyHyperlinks bool 91 | 92 | // The server supports images in the notifications. 93 | BodyImages bool 94 | 95 | // Supports markup in the body text. 96 | BodyMarkup bool 97 | 98 | // The server will render an animation of all the frames in a given 99 | // image array. 100 | IconMulti bool 101 | 102 | // Supports display of exactly 1 frame of any given image array. 103 | IconStatic bool 104 | 105 | // The server supports persistence of notifications. Notifications will 106 | // be retained until they are acknowledged or removed by the user or 107 | // recalled by the sender. 108 | Persistence bool 109 | 110 | // The server supports sounds on notifications. 111 | Sound bool 112 | } 113 | 114 | // GetCapabilities returns the capabilities of the notification server. 115 | func GetCapabilities() (c Capabilities, err error) { 116 | conn, err := dbus.SessionBus() 117 | if err != nil { 118 | return 119 | } 120 | 121 | var d = make(chan *dbus.Call, 1) 122 | var o = conn.Object(DbusInterfacePath, DbusObjectPath) 123 | var s = make([]string, 0) 124 | o.GoWithContext(context.Background(), 125 | CallGetCapabilities, 126 | 0, 127 | d) 128 | err = (<-d).Store(&s) 129 | if err != nil { 130 | return 131 | } 132 | 133 | for _, v := range s { 134 | switch v { 135 | case "action-icons": 136 | c.ActionIcons = true 137 | break 138 | case "actions": 139 | c.Actions = true 140 | break 141 | case "body": 142 | c.Body = true 143 | break 144 | case "body-hyperlinks": 145 | c.BodyHyperlinks = true 146 | break 147 | case "body-images": 148 | c.BodyImages = true 149 | break 150 | case "body-markup": 151 | c.BodyMarkup = true 152 | break 153 | case "icon-multi": 154 | c.IconMulti = true 155 | break 156 | case "icon-static": 157 | c.IconStatic = true 158 | break 159 | case "persistence": 160 | c.Persistence = true 161 | break 162 | case "sound": 163 | c.Sound = true 164 | break 165 | } 166 | } 167 | return 168 | } 169 | 170 | // ServerInformation is a struct containing information about the server such 171 | // as its name and version. 172 | type ServerInformation struct { 173 | // The name of the notification server daemon 174 | Name string 175 | 176 | // The vendor of the notification server 177 | Vendor string 178 | 179 | // Version of the notification server 180 | Version string 181 | 182 | // Spec version the notification server conforms to 183 | SpecVersion string 184 | } 185 | 186 | // GetServerInformation returns information about the notification server such 187 | // as its name and version. 188 | func GetServerInformation() (i ServerInformation, err error) { 189 | conn, err := dbus.SessionBus() 190 | if err != nil { 191 | return 192 | } 193 | var d = make(chan *dbus.Call, 1) 194 | var o = conn.Object(DbusInterfacePath, DbusObjectPath) 195 | o.GoWithContext(context.Background(), 196 | CallGetServerInformation, 197 | 0, 198 | d) 199 | err = (<-d).Store(&i.Name, 200 | &i.Vendor, 201 | &i.Version, 202 | &i.SpecVersion) 203 | return 204 | } 205 | 206 | // Notification is a struct which describes the notification to be displayed 207 | // by the notification server. 208 | type Notification struct { 209 | // The optional name of the application sending the notification. 210 | // Can be blank. 211 | AppName string 212 | 213 | // The optional notification ID that this notification replaces. 214 | ReplacesID uint32 215 | 216 | // The optional program icon of the calling application. 217 | AppIcon string 218 | 219 | // The summary text briefly describing the notification. 220 | Summary string 221 | 222 | // The optional detailed body text. 223 | Body string 224 | 225 | // The actions send a request message back to the notification client 226 | // when invoked. 227 | Actions []string 228 | 229 | // Hints are a way to provide extra data to a notification server. 230 | Hints map[string]interface{} 231 | 232 | // The timeout time in milliseconds since the display of the 233 | // notification at which the notification should automatically close. 234 | Timeout int32 235 | 236 | hints map[string]dbus.Variant 237 | } 238 | 239 | // NewNotification creates a new notification object with some basic 240 | // information. 241 | func NewNotification(summary, body string) Notification { 242 | return Notification{ 243 | Body: body, 244 | Summary: summary, 245 | Timeout: ExpiresDefault, 246 | hints: make(map[string]dbus.Variant), 247 | } 248 | } 249 | 250 | // Show sends the information in the notification object to the server to be 251 | // displayed. 252 | func (x *Notification) Show() (id uint32, err error) { 253 | conn, err := dbus.SessionBus() 254 | if err != nil { 255 | return 256 | } 257 | 258 | // We need to convert the interface type of the map to dbus.Variant as 259 | // people dont want to have to import the dbus package just to make use 260 | // of the notification hints. 261 | for k, v := range x.Hints { 262 | x.hints[k] = dbus.MakeVariant(v) 263 | } 264 | 265 | var d = make(chan *dbus.Call, 1) 266 | var o = conn.Object(DbusInterfacePath, DbusObjectPath) 267 | o.GoWithContext(context.Background(), 268 | CallNotify, 269 | 0, 270 | d, 271 | x.AppName, 272 | x.ReplacesID, 273 | x.AppIcon, 274 | x.Summary, 275 | x.Body, 276 | x.Actions, 277 | x.hints, 278 | x.Timeout) 279 | err = (<-d).Store(&id) 280 | return 281 | } 282 | 283 | // _ImageData needs to resemble the (iiibiiay) signature. 284 | type _ImageData struct { 285 | /*0*/ Width int 286 | /*1*/ Height int 287 | /*2*/ RowStride int 288 | /*3*/ HasAlpha bool 289 | /*4*/ BitsPerSample int 290 | /*5*/ Samples int 291 | /*6*/ Image []byte 292 | } 293 | 294 | type ImageError struct{} 295 | 296 | func (x ImageError) Error() string { 297 | return "Given image.Image was not of type *image.RGBA" 298 | } 299 | 300 | // SetImage sets the image in the notification from an image.Image 301 | // interface which must have an underlying type of *image.RGBA. 302 | // Only the RGBA color space is allowed as only that is supported 303 | // by the gdk-pixbuf library. 304 | func (x *Notification) SetImage(img image.Image) (err error) { 305 | if p, ok := img.(*image.RGBA); ok { 306 | var r = p.Bounds() 307 | var d = _ImageData{ 308 | r.Max.X, // Width 309 | r.Max.Y, // Height 310 | p.Stride, 311 | true, 312 | 8, 313 | 4, 314 | p.Pix, 315 | } 316 | x.hints["image-data"] = dbus.MakeVariant(d) 317 | return 318 | } 319 | err = ImageError{} 320 | return 321 | } 322 | 323 | // CloseNotification closes the notification if it exists using its id. 324 | func CloseNotification(id uint32) (err error) { 325 | conn, err := dbus.SessionBus() 326 | if err != nil { 327 | return 328 | } 329 | var d = make(chan *dbus.Call, 1) 330 | var o = conn.Object(DbusInterfacePath, DbusObjectPath) 331 | o.GoWithContext(context.Background(), 332 | CallCloseNotification, 333 | 0, 334 | d, 335 | id) 336 | err = (<-d).Err 337 | return 338 | } 339 | -------------------------------------------------------------------------------- /dbus_test.go: -------------------------------------------------------------------------------- 1 | package notify 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestGetCapabilities(t *testing.T) { 8 | c, err := GetCapabilities() 9 | if err != nil { 10 | t.Fatal(err) 11 | } 12 | 13 | t.Logf("Support Action Icons: %v\n", c.ActionIcons) 14 | t.Logf("Support Actions: %v\n", c.Actions) 15 | t.Logf("Support Body: %v\n", c.Body) 16 | t.Logf("Support Body Hyperlinks: %v\n", c.BodyHyperlinks) 17 | t.Logf("Support Body Images: %v\n", c.BodyImages) 18 | t.Logf("Support Body Markup: %v\n", c.BodyMarkup) 19 | t.Logf("Support Icon Multi: %v\n", c.IconMulti) 20 | t.Logf("Support Icon Static: %v\n", c.IconStatic) 21 | t.Logf("Support Persistence: %v\n", c.Persistence) 22 | t.Logf("Support Sound: %v\n", c.Sound) 23 | } 24 | 25 | func TestGetServerInformation(t *testing.T) { 26 | info, err := GetServerInformation() 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | 31 | t.Logf("Server Name: %s\n", info.Name) 32 | t.Logf("Server Spec Version: %s\n", info.SpecVersion) 33 | t.Logf("Server Vendor: %s\n", info.Vendor) 34 | t.Logf("Server Version: %s\n", info.Version) 35 | } 36 | 37 | func TestNewNotification(t *testing.T) { 38 | ntf := NewNotification("Notification Test", "Just a test") 39 | if _, err := ntf.Show(); err != nil { 40 | t.Fatal(err) 41 | } 42 | } 43 | 44 | func TestCloseNotification(t *testing.T) { 45 | ntf := NewNotification("Notification Test", "Just a test") 46 | id, err := ntf.Show() 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | 51 | if err = CloseNotification(id); err != nil { 52 | t.Fatal(err) 53 | } 54 | } 55 | 56 | func TestUrgencyNotification(t *testing.T) { 57 | ntfLow := NewNotification("Urgency Test", "Testing notification urgency low") 58 | ntfLow.Hints = make(map[string]interface{}) 59 | 60 | ntfLow.Hints[HintUrgency] = UrgencyLow 61 | _, err := ntfLow.Show() 62 | if err != nil { 63 | t.Fatal(err) 64 | } 65 | 66 | ntfCritical := NewNotification("Urgency Test", "Testing notification urgency critical") 67 | ntfCritical.Hints = make(map[string]interface{}) 68 | 69 | ntfCritical.Hints[HintUrgency] = UrgencyCritical 70 | _, err = ntfCritical.Show() 71 | if err != nil { 72 | t.Fatal(err) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /*Package notify provides an implementation of the Gnome DBus notifications 2 | specification.*/ 3 | package notify 4 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/TheCreeper/go-notify 2 | 3 | go 1.12 4 | 5 | require github.com/godbus/dbus/v5 v5.0.3 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME= 2 | github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 3 | --------------------------------------------------------------------------------