From 137b32f46e2c6a449103f19f1fe5334131f731bb Mon Sep 17 00:00:00 2001 From: emersion Date: Sat, 24 Oct 2015 12:43:25 +0200 Subject: [PATCH 1/6] Adds signals support --- notification.go | 59 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/notification.go b/notification.go index 52bbdda..5196265 100644 --- a/notification.go +++ b/notification.go @@ -18,9 +18,14 @@ const ( // New creates a new Notificator using conn func New(conn *dbus.Conn) Notifier { - return ¬ifier{ + n := ¬ifier{ conn: conn, } + + n.conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, + "type='signal',path='"+objectPath+"',interface='"+dbusNotificationsInterface+"'") + + return n } // notifier implements Notificator @@ -171,6 +176,56 @@ func (n *notifier) GetServerInformation() (ServerInformation, error) { return ret, nil } +type NotificationClosedSignal struct { + Id uint32 + Reason uint32 +} + +func (n *notifier) NotificationClosed(closed chan<- *NotificationClosedSignal) { + c := make(chan *dbus.Signal, len(closed)) + + go (func() { + for { + signal := <-c + if signal.Name != dbusNotificationsInterface+".NotificationClosed" { + continue + } + + closed <- &NotificationClosedSignal{ + Id: signal.Body[0].(uint32), + Reason: signal.Body[1].(uint32), + } + } + })() + + n.conn.Signal(c) +} + +type ActionInvokedSignal struct { + Id uint32 + ActionKey string +} + +func (n *notifier) ActionInvoked(action chan<- *ActionInvokedSignal) { + c := make(chan *dbus.Signal, len(action)) + + go (func() { + for { + signal := <-c + if signal.Name != dbusNotificationsInterface+".ActionInvoked" { + continue + } + + action <- &ActionInvokedSignal{ + Id: signal.Body[0].(uint32), + ActionKey: signal.Body[1].(string), + } + } + })() + + n.conn.Signal(c) +} + // Notifier is an interface for implementing the operations supported by the // freedesktop DBus Notifications object. type Notifier interface { @@ -178,4 +233,6 @@ type Notifier interface { GetCapabilities() ([]string, error) GetServerInformation() (ServerInformation, error) CloseNotification(id int) (bool, error) + NotificationClosed(closed chan<- *NotificationClosedSignal) + ActionInvoked(action chan<- *ActionInvokedSignal) } From 7c3bf701559211579c9d1240c5350eccd4a0f0b2 Mon Sep 17 00:00:00 2001 From: Eivind Siqveland Larsen Date: Sat, 24 Oct 2015 14:04:54 +0200 Subject: [PATCH 2/6] [signals] add dbus signals for notifications --- example/main.go | 8 +++- notification.go | 123 ++++++++++++++++++++++++++++++------------------ 2 files changed, 82 insertions(+), 49 deletions(-) diff --git a/example/main.go b/example/main.go index 70d7f34..58eab87 100644 --- a/example/main.go +++ b/example/main.go @@ -17,6 +17,7 @@ func main() { } notificator := notify.New(conn) + defer notificator.Close() n := notify.Notification{ AppName: "Test GO App", @@ -35,6 +36,9 @@ func main() { } log.Printf("sent notification id: %v", id) + not := <-notificator.NotificationClosed() + log.Printf("NotificationClosed: %v Reason: %v", not.Id, not.Reason) + caps, err := notificator.GetCapabilities() if err != nil { log.Printf("error fetching capabilities: %v", err) @@ -52,7 +56,7 @@ func main() { fmt.Printf("Version: %v\n", info.Version) fmt.Printf("Spec: %v\n", info.SpecVersion) - // And there is a helper for just sending notifications directly: - notify.SendNotification(conn, n) + // And there is a helper for just sending notifications directly if you have a connection: + //notify.SendNotification(conn, n) } diff --git a/notification.go b/notification.go index 5196265..29b09f8 100644 --- a/notification.go +++ b/notification.go @@ -8,29 +8,77 @@ import ( ) const ( - objectPath = "/org/freedesktop/Notifications" // the DBUS object path - dbusNotificationsInterface = "org.freedesktop.Notifications" // DBUS Interface - getCapabilities = "org.freedesktop.Notifications.GetCapabilities" - closeNotification = "org.freedesktop.Notifications.CloseNotification" - notify = "org.freedesktop.Notifications.Notify" - getServerInformation = "org.freedesktop.Notifications.GetServerInformation" + objectPath = "/org/freedesktop/Notifications" // the DBUS object path + dbusNotificationsInterface = "org.freedesktop.Notifications" // DBUS Interface + dbusNotificationClosed = "org.freedesktop.Notifications.NotificationClosed" + dbusNotificationActionInvoked = "org.freedesktop.Notifications.ActionInvoked" + getCapabilities = "org.freedesktop.Notifications.GetCapabilities" + closeNotification = "org.freedesktop.Notifications.CloseNotification" + notify = "org.freedesktop.Notifications.Notify" + getServerInformation = "org.freedesktop.Notifications.GetServerInformation" ) // New creates a new Notificator using conn +// New sets up a Notifier that listenes on dbus' signals regarding +// Notifications, e.g. func New(conn *dbus.Conn) Notifier { n := ¬ifier{ - conn: conn, + conn: conn, + signal: make(chan *dbus.Signal, 5), + closer: make(chan *NotificationClosedSignal, 5), + action: make(chan *ActionInvokedSignal, 5), + done: make(chan bool), } n.conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, "type='signal',path='"+objectPath+"',interface='"+dbusNotificationsInterface+"'") + go (func() { + received := 0 + for { + select { + case signal := <-n.signal: + received += 1 + log.Printf("got signal: %v Signal: %+v", received, signal) + n.handleSignal(signal) + // its all over, exit and go home + case <-n.done: + log.Printf("its all over, go home") + return + } + } + })() + + // setup signal reception + n.conn.Signal(n.signal) + return n } +func (n notifier) handleSignal(signal *dbus.Signal) { + switch signal.Name { + case dbusNotificationClosed: + n.closer <- &NotificationClosedSignal{ + Id: signal.Body[0].(uint32), + Reason: signal.Body[1].(uint32), + } + case dbusNotificationActionInvoked: + n.action <- &ActionInvokedSignal{ + Id: signal.Body[0].(uint32), + ActionKey: signal.Body[1].(string), + } + default: + log.Printf("unknown signal: %+v", signal) + } +} + // notifier implements Notificator type notifier struct { - conn *dbus.Conn + conn *dbus.Conn + signal chan *dbus.Signal + closer chan *NotificationClosedSignal + action chan *ActionInvokedSignal + done chan bool } // Notification holds all information needed for creating a notification @@ -177,53 +225,33 @@ func (n *notifier) GetServerInformation() (ServerInformation, error) { } type NotificationClosedSignal struct { - Id uint32 + Id uint32 Reason uint32 } -func (n *notifier) NotificationClosed(closed chan<- *NotificationClosedSignal) { - c := make(chan *dbus.Signal, len(closed)) - - go (func() { - for { - signal := <-c - if signal.Name != dbusNotificationsInterface+".NotificationClosed" { - continue - } - - closed <- &NotificationClosedSignal{ - Id: signal.Body[0].(uint32), - Reason: signal.Body[1].(uint32), - } - } - })() - - n.conn.Signal(c) +func (n *notifier) NotificationClosed() <-chan *NotificationClosedSignal { + return n.closer } type ActionInvokedSignal struct { - Id uint32 + Id uint32 ActionKey string } -func (n *notifier) ActionInvoked(action chan<- *ActionInvokedSignal) { - c := make(chan *dbus.Signal, len(action)) - - go (func() { - for { - signal := <-c - if signal.Name != dbusNotificationsInterface+".ActionInvoked" { - continue - } - - action <- &ActionInvokedSignal{ - Id: signal.Body[0].(uint32), - ActionKey: signal.Body[1].(string), - } - } - })() +func (n *notifier) ActionInvoked() <-chan *ActionInvokedSignal { + return n.action +} - n.conn.Signal(c) +// Close cleans up and shuts down signal delivery loop +func (n *notifier) Close() error { + // remove signal reception + n.done <- true + defer n.conn.Signal(n.signal) + close(n.closer) + close(n.action) + close(n.done) + err := n.conn.Close() + return err } // Notifier is an interface for implementing the operations supported by the @@ -233,6 +261,7 @@ type Notifier interface { GetCapabilities() ([]string, error) GetServerInformation() (ServerInformation, error) CloseNotification(id int) (bool, error) - NotificationClosed(closed chan<- *NotificationClosedSignal) - ActionInvoked(action chan<- *ActionInvokedSignal) + NotificationClosed() <-chan *NotificationClosedSignal + ActionInvoked() <-chan *ActionInvokedSignal + Close() error } From f01e317a3d1b38ce151ad774e34797fc3cef8d70 Mon Sep 17 00:00:00 2001 From: Eivind Siqveland Larsen Date: Sat, 24 Oct 2015 14:25:29 +0200 Subject: [PATCH 3/6] [signals] rework some code and handle signals in a routine --- example/main.go | 5 ++++- notification.go | 39 ++++++++++++++++++++++++--------------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/example/main.go b/example/main.go index 58eab87..33436ee 100644 --- a/example/main.go +++ b/example/main.go @@ -16,7 +16,10 @@ func main() { panic(err) } - notificator := notify.New(conn) + notificator, err := notify.New(conn) + if err != nil { + log.Fatalln(err.Error()) + } defer notificator.Close() n := notify.Notification{ diff --git a/notification.go b/notification.go index 29b09f8..d953b45 100644 --- a/notification.go +++ b/notification.go @@ -8,20 +8,22 @@ import ( ) const ( - objectPath = "/org/freedesktop/Notifications" // the DBUS object path - dbusNotificationsInterface = "org.freedesktop.Notifications" // DBUS Interface - dbusNotificationClosed = "org.freedesktop.Notifications.NotificationClosed" - dbusNotificationActionInvoked = "org.freedesktop.Notifications.ActionInvoked" - getCapabilities = "org.freedesktop.Notifications.GetCapabilities" - closeNotification = "org.freedesktop.Notifications.CloseNotification" - notify = "org.freedesktop.Notifications.Notify" - getServerInformation = "org.freedesktop.Notifications.GetServerInformation" + dbusRemoveMatch = "org.freedesktop.DBus.RemoveMatch" + dbusAddMatch = "org.freedesktop.DBus.AddMatch" + objectPath = "/org/freedesktop/Notifications" // the DBUS object path + dbusNotificationsInterface = "org.freedesktop.Notifications" // DBUS Interface + signalNotificationClosed = "org.freedesktop.Notifications.NotificationClosed" + signalNotificationActionInvoked = "org.freedesktop.Notifications.ActionInvoked" + getCapabilities = "org.freedesktop.Notifications.GetCapabilities" + closeNotification = "org.freedesktop.Notifications.CloseNotification" + notify = "org.freedesktop.Notifications.Notify" + getServerInformation = "org.freedesktop.Notifications.GetServerInformation" ) // New creates a new Notificator using conn // New sets up a Notifier that listenes on dbus' signals regarding // Notifications, e.g. -func New(conn *dbus.Conn) Notifier { +func New(conn *dbus.Conn) (Notifier, error) { n := ¬ifier{ conn: conn, signal: make(chan *dbus.Signal, 5), @@ -30,8 +32,11 @@ func New(conn *dbus.Conn) Notifier { done: make(chan bool), } - n.conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, + call := n.conn.BusObject().Call(dbusAddMatch, 0, "type='signal',path='"+objectPath+"',interface='"+dbusNotificationsInterface+"'") + if call.Err != nil { + return nil, call.Err + } go (func() { received := 0 @@ -40,7 +45,7 @@ func New(conn *dbus.Conn) Notifier { case signal := <-n.signal: received += 1 log.Printf("got signal: %v Signal: %+v", received, signal) - n.handleSignal(signal) + go n.handleSignal(signal) // its all over, exit and go home case <-n.done: log.Printf("its all over, go home") @@ -52,17 +57,17 @@ func New(conn *dbus.Conn) Notifier { // setup signal reception n.conn.Signal(n.signal) - return n + return n, nil } func (n notifier) handleSignal(signal *dbus.Signal) { switch signal.Name { - case dbusNotificationClosed: + case signalNotificationClosed: n.closer <- &NotificationClosedSignal{ Id: signal.Body[0].(uint32), Reason: signal.Body[1].(uint32), } - case dbusNotificationActionInvoked: + case signalNotificationActionInvoked: n.action <- &ActionInvokedSignal{ Id: signal.Body[0].(uint32), ActionKey: signal.Body[1].(string), @@ -244,8 +249,12 @@ func (n *notifier) ActionInvoked() <-chan *ActionInvokedSignal { // Close cleans up and shuts down signal delivery loop func (n *notifier) Close() error { - // remove signal reception + log.Printf("closing!") n.done <- true + n.conn.BusObject().Call(dbusRemoveMatch, 0, + "type='signal',path='"+objectPath+"',interface='"+dbusNotificationsInterface+"'") + + // remove signal reception defer n.conn.Signal(n.signal) close(n.closer) close(n.action) From 5475bfba1685d11d112bea0393fed03669614bd0 Mon Sep 17 00:00:00 2001 From: Eivind Siqveland Larsen Date: Sun, 25 Oct 2015 15:47:19 +0100 Subject: [PATCH 4/6] [signals] more work on signal delivery --- doc.go | 13 ++-- example/main.go | 20 +++--- notification.go | 187 ++++++++++++++++++++++++++++-------------------- 3 files changed, 129 insertions(+), 91 deletions(-) diff --git a/doc.go b/doc.go index 16553a9..e9db54e 100644 --- a/doc.go +++ b/doc.go @@ -3,11 +3,16 @@ The notify package is a wrapper around godbus for dbus notification interface See: https://developer.gnome.org/notification-spec/ and https://github.com/godbus/dbus -Each notification displayed is allocated a unique ID by the server. (see Notify) -This ID unique within the dbus session. While the notification server is running, -the ID will not be recycled unless the capacity of a uint32 is exceeded. +The package provides exported methods for simple usage, e.g. just show a notification. +It also provides the interface Notifier that includes event delivery for notifications. +Note that if you use New() to create a notifier, it is the caller responsibility to also drain the +channels for ActionInvoked() and NotificationClosed(). -This can be used to hide the notification before the expiration timeout is reached. (see CloseNotification) +Each notification created is allocated a unique ID by the server (see Notification). +This ID is unique within the dbus session, and is an increasing number. +While the notification server is running, the ID will not be recycled unless the capacity of a uint32 is exceeded. + +The ID can be used to hide the notification before the expiration timeout is reached (see CloseNotification). The ID can also be used to atomically replace the notification with another (Notification.ReplaceID). This allows you to (for instance) modify the contents of a notification while it's on-screen. diff --git a/example/main.go b/example/main.go index 33436ee..a287e6b 100644 --- a/example/main.go +++ b/example/main.go @@ -16,11 +16,11 @@ func main() { panic(err) } - notificator, err := notify.New(conn) + notifier, err := notify.New(conn) if err != nil { log.Fatalln(err.Error()) } - defer notificator.Close() + defer notifier.Close() n := notify.Notification{ AppName: "Test GO App", @@ -28,21 +28,25 @@ func main() { AppIcon: iconName, Summary: "Test", Body: "This is a test of the DBus bindings for go.", - Actions: []string{}, + Actions: []string{"cancel", "Cancel", "open", "Open"}, // tuples of (action_key, label) Hints: map[string]dbus.Variant{}, ExpireTimeout: int32(5000), } - id, err := notificator.SendNotification(n) + id, err := notifier.SendNotification(n) if err != nil { panic(err) } log.Printf("sent notification id: %v", id) - not := <-notificator.NotificationClosed() - log.Printf("NotificationClosed: %v Reason: %v", not.Id, not.Reason) + actions := notifier.ActionInvoked() + action := <-actions + log.Printf("Action: %v Key: %v", action.Id, action.ActionKey) - caps, err := notificator.GetCapabilities() + closer := <-notifier.NotificationClosed() + log.Printf("NotificationClosed: %v Reason: %v", closer.Id, closer.Reason) + + caps, err := notifier.GetCapabilities() if err != nil { log.Printf("error fetching capabilities: %v", err) } @@ -50,7 +54,7 @@ func main() { fmt.Printf("Registered capability: %v\n", caps[x]) } - info, err := notificator.GetServerInformation() + info, err := notifier.GetServerInformation() if err != nil { log.Printf("error getting server information: %v", err) } diff --git a/notification.go b/notification.go index d953b45..d6c6d96 100644 --- a/notification.go +++ b/notification.go @@ -5,61 +5,80 @@ import ( "log" "github.com/godbus/dbus" + "sync" ) const ( - dbusRemoveMatch = "org.freedesktop.DBus.RemoveMatch" - dbusAddMatch = "org.freedesktop.DBus.AddMatch" - objectPath = "/org/freedesktop/Notifications" // the DBUS object path - dbusNotificationsInterface = "org.freedesktop.Notifications" // DBUS Interface - signalNotificationClosed = "org.freedesktop.Notifications.NotificationClosed" - signalNotificationActionInvoked = "org.freedesktop.Notifications.ActionInvoked" - getCapabilities = "org.freedesktop.Notifications.GetCapabilities" - closeNotification = "org.freedesktop.Notifications.CloseNotification" - notify = "org.freedesktop.Notifications.Notify" - getServerInformation = "org.freedesktop.Notifications.GetServerInformation" + dbusRemoveMatch = "org.freedesktop.DBus.RemoveMatch" + dbusAddMatch = "org.freedesktop.DBus.AddMatch" + dbusObjectPath = "/org/freedesktop/Notifications" // the DBUS object path + dbusNotificationsInterface = "org.freedesktop.Notifications" // DBUS Interface + signalNotificationClosed = "org.freedesktop.Notifications.NotificationClosed" + signalActionInvoked = "org.freedesktop.Notifications.ActionInvoked" + callGetCapabilities = "org.freedesktop.Notifications.GetCapabilities" + callCloseNotification = "org.freedesktop.Notifications.CloseNotification" + callNotify = "org.freedesktop.Notifications.Notify" + callGetServerInformation = "org.freedesktop.Notifications.GetServerInformation" + + channelBufferSize = 10 ) -// New creates a new Notificator using conn -// New sets up a Notifier that listenes on dbus' signals regarding -// Notifications, e.g. +// New creates a new Notifier using conn. +// Sets up a Notifier that listens on dbus' signals regarding +// Notifications: NotificationClosed and ActionInvoked. +// +// Note this also means the caller MUST consume output from these channels, +// given in methods NotificationClosed() and ActionInvoked(). +// Users that only want to send a simple notification, but don't care about +// interactions, see exported method: SendNotification(conn, Notification) +// +// Caller is also responsible to call Close() before exiting, +// to shut down event loop and cleanup. func New(conn *dbus.Conn) (Notifier, error) { n := ¬ifier{ - conn: conn, - signal: make(chan *dbus.Signal, 5), - closer: make(chan *NotificationClosedSignal, 5), - action: make(chan *ActionInvokedSignal, 5), - done: make(chan bool), + conn: conn, + signal: make(chan *dbus.Signal, channelBufferSize), + closer: make(chan *NotificationClosedSignal, channelBufferSize), + action: make(chan *ActionInvokedSignal, channelBufferSize), + done: make(chan bool), + running: sync.Mutex{}, } + // add a listener in dbus for signals to Notification interface. call := n.conn.BusObject().Call(dbusAddMatch, 0, - "type='signal',path='"+objectPath+"',interface='"+dbusNotificationsInterface+"'") + "type='signal',path='"+dbusObjectPath+"',interface='"+dbusNotificationsInterface+"'") if call.Err != nil { return nil, call.Err } - go (func() { - received := 0 - for { - select { - case signal := <-n.signal: - received += 1 - log.Printf("got signal: %v Signal: %+v", received, signal) - go n.handleSignal(signal) - // its all over, exit and go home - case <-n.done: - log.Printf("its all over, go home") - return - } - } - })() + // start eventloop + go n.eventLoop() - // setup signal reception + // register in dbus for signal delivery n.conn.Signal(n.signal) return n, nil } +func (n notifier) eventLoop() { + n.running.Lock() + defer n.running.Unlock() + received := 0 + for { + select { + case signal := <-n.signal: + received += 1 + log.Printf("got signal: %v Signal: %+v", received, signal) + go n.handleSignal(signal) + // its all over, exit and go home + case <-n.done: + log.Printf("its all over, go home") + return + } + } +} + +// signal handler that translates and sends notifications to channels func (n notifier) handleSignal(signal *dbus.Signal) { switch signal.Name { case signalNotificationClosed: @@ -67,7 +86,7 @@ func (n notifier) handleSignal(signal *dbus.Signal) { Id: signal.Body[0].(uint32), Reason: signal.Body[1].(uint32), } - case signalNotificationActionInvoked: + case signalActionInvoked: n.action <- &ActionInvokedSignal{ Id: signal.Body[0].(uint32), ActionKey: signal.Body[1].(string), @@ -77,25 +96,26 @@ func (n notifier) handleSignal(signal *dbus.Signal) { } } -// notifier implements Notificator +// notifier implements Notifier interface type notifier struct { - conn *dbus.Conn - signal chan *dbus.Signal - closer chan *NotificationClosedSignal - action chan *ActionInvokedSignal - done chan bool + conn *dbus.Conn + signal chan *dbus.Signal + closer chan *NotificationClosedSignal + action chan *ActionInvokedSignal + done chan bool + running sync.Mutex } // Notification holds all information needed for creating a notification type Notification struct { AppName string - ReplacesID uint32 - AppIcon string // see icons here: http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html + ReplacesID uint32 // (atomically) replaces notification with this ID. Optional. + AppIcon string // See icons here: http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html Optional. Summary string Body string - Actions []string + Actions []string // tuples of (action_key, label), e.g.: []string{"cancel", "Cancel", "open", "Open"} Hints map[string]dbus.Variant - ExpireTimeout int32 // millisecond to show notification + ExpireTimeout int32 // milliseconds to show notification } // SendNotification sends a notification to the notification server. @@ -127,11 +147,11 @@ func (n *notifier) SendNotification(note Notification) (uint32, error) { return SendNotification(n.conn, note) } -// SendNotification is same as Notifier.SendNotification -// Provided for convenience. +// SendNotification is provided for convenience. +// Use if you only want to deliver a notification and dont care about events. func SendNotification(conn *dbus.Conn, note Notification) (uint32, error) { - obj := conn.Object(dbusNotificationsInterface, objectPath) - call := obj.Call(notify, 0, + obj := conn.Object(dbusNotificationsInterface, dbusObjectPath) + call := obj.Call(callNotify, 0, note.AppName, note.ReplacesID, note.AppIcon, @@ -152,27 +172,6 @@ func SendNotification(conn *dbus.Conn, note Notification) (uint32, error) { return ret, nil } -// GetCapabilities gets the capabilities of the notification server. -// This call takes no parameters. -// It returns an array of strings. Each string describes an optional capability implemented by the server. -// -// See also: https://developer.gnome.org/notification-spec/ -func (n *notifier) GetCapabilities() ([]string, error) { - obj := n.conn.Object(dbusNotificationsInterface, objectPath) - call := obj.Call(getCapabilities, 0) - if call.Err != nil { - log.Printf("error calling GetCapabilities: %v", call.Err) - return []string{}, call.Err - } - var ret []string - err := call.Store(&ret) - if err != nil { - log.Printf("error getting capabilities ret value: %v", err) - return ret, err - } - return ret, nil -} - // CloseNotification causes a notification to be forcefully closed and removed from the user's view. // It can be used, for example, in the event that what the notification pertains to is no longer relevant, // or to cancel a notification with no expiration time. @@ -180,8 +179,8 @@ func (n *notifier) GetCapabilities() ([]string, error) { // The NotificationClosed (dbus) signal is emitted by this method. // If the notification no longer exists, an empty D-BUS Error message is sent back. func (n *notifier) CloseNotification(id int) (bool, error) { - obj := n.conn.Object(dbusNotificationsInterface, objectPath) - call := obj.Call(closeNotification, 0, uint32(id)) + obj := n.conn.Object(dbusNotificationsInterface, dbusObjectPath) + call := obj.Call(callCloseNotification, 0, uint32(id)) if call.Err != nil { return false, call.Err } @@ -209,21 +208,51 @@ type ServerInformation struct { // version STRING The server's version number. // spec_version STRING The specification version the server is compliant with. // -func (n *notifier) GetServerInformation() (ServerInformation, error) { - obj := n.conn.Object(dbusNotificationsInterface, objectPath) +func GetServerInformation(conn *dbus.Conn) (ServerInformation, error) { + obj := conn.Object(dbusNotificationsInterface, dbusObjectPath) if obj == nil { return ServerInformation{}, errors.New("error creating dbus call object") } - call := obj.Call(getServerInformation, 0) + call := obj.Call(callGetServerInformation, 0) if call.Err != nil { - log.Printf("Error calling %v: %v", getServerInformation, call.Err) + log.Printf("Error calling %v: %v", callGetServerInformation, call.Err) return ServerInformation{}, call.Err } ret := ServerInformation{} err := call.Store(&ret.Name, &ret.Vendor, &ret.Version, &ret.SpecVersion) if err != nil { - log.Printf("error reading %v return values: %v", getServerInformation, err) + log.Printf("error reading %v return values: %v", callGetServerInformation, err) + return ret, err + } + return ret, nil +} + +func (n *notifier) GetServerInformation() (ServerInformation, error) { + return GetServerInformation(n.conn) +} + +// GetCapabilities gets the capabilities of the notification server. +// This call takes no parameters. +// It returns an array of strings. Each string describes an optional capability implemented by the server. +// +// See also: https://developer.gnome.org/notification-spec/ +func (n *notifier) GetCapabilities() ([]string, error) { + return GetCapabilities(n.conn) +} + +// GetCapabilities provide an exported method for this operation +func GetCapabilities(conn *dbus.Conn) ([]string, error) { + obj := conn.Object(dbusNotificationsInterface, dbusObjectPath) + call := obj.Call(callGetCapabilities, 0) + if call.Err != nil { + log.Printf("error calling GetCapabilities: %v", call.Err) + return []string{}, call.Err + } + var ret []string + err := call.Store(&ret) + if err != nil { + log.Printf("error getting capabilities ret value: %v", err) return ret, err } return ret, nil @@ -252,7 +281,7 @@ func (n *notifier) Close() error { log.Printf("closing!") n.done <- true n.conn.BusObject().Call(dbusRemoveMatch, 0, - "type='signal',path='"+objectPath+"',interface='"+dbusNotificationsInterface+"'") + "type='signal',path='"+dbusObjectPath+"',interface='"+dbusNotificationsInterface+"'") // remove signal reception defer n.conn.Signal(n.signal) From ecb23be127d2729934151007be9f94f40a35ab32 Mon Sep 17 00:00:00 2001 From: Eivind Siqveland Larsen Date: Sun, 25 Oct 2015 15:55:35 +0100 Subject: [PATCH 5/6] [example] refactor --- example/main.go | 55 +++++++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/example/main.go b/example/main.go index a287e6b..5a77269 100644 --- a/example/main.go +++ b/example/main.go @@ -9,19 +9,15 @@ import ( ) func main() { - iconName := "mail-unread" conn, err := dbus.SessionBus() if err != nil { panic(err) } - notifier, err := notify.New(conn) - if err != nil { - log.Fatalln(err.Error()) - } - defer notifier.Close() - + // Basic usage + // Create a Notification to send + iconName := "mail-unread" n := notify.Notification{ AppName: "Test GO App", ReplacesID: uint32(0), @@ -33,20 +29,16 @@ func main() { ExpireTimeout: int32(5000), } - id, err := notifier.SendNotification(n) + // Ship it! + createdID, err := notify.SendNotification(conn, n) if err != nil { - panic(err) + log.Printf("error sending notification: %v", err.Error()) } - log.Printf("sent notification id: %v", id) - - actions := notifier.ActionInvoked() - action := <-actions - log.Printf("Action: %v Key: %v", action.Id, action.ActionKey) + log.Printf("created notification with id: %v", createdID) - closer := <-notifier.NotificationClosed() - log.Printf("NotificationClosed: %v Reason: %v", closer.Id, closer.Reason) - caps, err := notifier.GetCapabilities() + // List server features! + caps, err := notify.GetCapabilities(conn) if err != nil { log.Printf("error fetching capabilities: %v", err) } @@ -54,7 +46,7 @@ func main() { fmt.Printf("Registered capability: %v\n", caps[x]) } - info, err := notifier.GetServerInformation() + info, err := notify.GetServerInformation(conn) if err != nil { log.Printf("error getting server information: %v", err) } @@ -63,7 +55,30 @@ func main() { fmt.Printf("Version: %v\n", info.Version) fmt.Printf("Spec: %v\n", info.SpecVersion) - // And there is a helper for just sending notifications directly if you have a connection: - //notify.SendNotification(conn, n) + + + // Notifyer interface with event delivery + notifier, err := notify.New(conn) + if err != nil { + log.Fatalln(err.Error()) + } + defer notifier.Close() + + id, err := notifier.SendNotification(n) + if err != nil { + log.Printf("error sending notification: %v", err) + } + log.Printf("sent notification id: %v", id) + + // Listen for actions invoked! + actions := notifier.ActionInvoked() + go func() { + action := <-actions + log.Printf("ActionInvoked: %v Key: %v", action.Id, action.ActionKey) + }() + + closer := <-notifier.NotificationClosed() + log.Printf("NotificationClosed: %v Reason: %v", closer.Id, closer.Reason) + } From f6e56fa45a94914ab6f16c49c39074e62c83e97a Mon Sep 17 00:00:00 2001 From: Eivind Siqveland Larsen Date: Sun, 25 Oct 2015 16:06:01 +0100 Subject: [PATCH 6/6] [docs] more work --- README.md | 55 +++++++++---------------------------------------- notification.go | 5 +++++ 2 files changed, 15 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 67519ac..c45f817 100644 --- a/README.md +++ b/README.md @@ -6,66 +6,31 @@ Notify is a go library for interacting with the dbus notification service define https://developer.gnome.org/notification-spec/ It can deliver notifications to desktop using dbus communication, ala how libnotify does it. -It has so far only been testing with gnome and gnome-shell 3.16 in Arch Linux. +It has so far only been testing with gnome and gnome-shell 3.16/3.18 in Arch Linux. Please note ```notify``` is still in a very early change and no APIs are locked until a 1.0 is released. More testers are very welcome =) -Depends on github.com/godbus/dbus. +Depends on: + - [godbus](https://github.com/godbus/dbus). ## Quick intro +See example: [main.go](https://github.com/esiqveland/notify/blob/master/example/main.go). + +Clone repo and go to examples folder: + +``` go run main.go ``` -```go -package main - -import ( - "github.com/esiqveland/notify" - "github.com/godbus/dbus" - "log" -) - -func main() { - conn, err := dbus.SessionBus() - if err != nil { - panic(err) - } - - notifier := notify.New(conn) - - n := notify.Notification{ - AppName: "Test GO App", - ReplacesID: uint32(0), - AppIcon: "mail-unread", - Summary: "Test", - Body: "This is a test of the DBus bindings for go.", - Actions: []string{}, - Hints: map[string]dbus.Variant{}, - ExpireTimeout: int32(5000), - } - - id, err := notifier.SendNotification(n) - if err != nil { - panic(err) - } - log.Printf("sent notification id: %v", id) - - // And there is a helper for just sending notifications directly: - notify.SendNotification(conn, n) -} - -``` - -You should now have gotten this notification delivered to your desktop. ## TODO -- [ ] Add callback support aka dbus signals? +- [x] Add callback support aka dbus signals. - [ ] Tests. I am very interested in any ideas for writing some (useful) tests for this. ## See also -`main.go` in `examples/` directory and https://developer.gnome.org/notification-spec/ +The Gnome notification spec https://developer.gnome.org/notification-spec/. ## License diff --git a/notification.go b/notification.go index d6c6d96..69d30df 100644 --- a/notification.go +++ b/notification.go @@ -258,6 +258,11 @@ func GetCapabilities(conn *dbus.Conn) ([]string, error) { return ret, nil } +// From the Gnome developer spec: +const ReasonExpired = 1 // 1 - The notification expired. +const ReasonDismissedByUser = 2 // 2 - The notification was dismissed by the user. +const ReasonClosedByCall = 3 // 3 - The notification was closed by a call to CloseNotification. +const ReasonUnknown = 4 // 4 - Undefined/reserved reasons. type NotificationClosedSignal struct { Id uint32 Reason uint32