From c404b1f22435f01609acb55e48d4b6419d5626ec Mon Sep 17 00:00:00 2001 From: Chris Miller Date: Sat, 9 Feb 2019 22:18:09 +0000 Subject: [PATCH] Added SignalNotify to get action and close signals --- notify.go | 97 +++++++++++++++++++++++++++++++++++++++++++++++++- notify_test.go | 53 ++++++++++++++++++++++++++- 2 files changed, 148 insertions(+), 2 deletions(-) diff --git a/notify.go b/notify.go index 6810008..0c3f186 100644 --- a/notify.go +++ b/notify.go @@ -2,7 +2,11 @@ // specification. package notify -import "github.com/godbus/dbus" +import ( + "context" + + "github.com/godbus/dbus" +) // Notification object paths and interfaces. const ( @@ -287,3 +291,94 @@ func CloseNotification(id uint32) (err error) { err = call.Err return } + +// CloseReason is the reason why the notification was closed. +type CloseReason uint32 + +func (x CloseReason) String() string { + switch x { + case NotClosed: + return "not closed" + case CloseReasonExpired: + return "expired" + case CloseReasonUserDismissed: + return "user dismissed" + case CloseReasonCloseCalled: + return "close called" + default: + return "undefined" + } +} + +// https://developer.gnome.org/notification-spec/#signals +const ( + NotClosed CloseReason = 0 + CloseReasonExpired CloseReason = 1 + CloseReasonUserDismissed CloseReason = 2 + CloseReasonCloseCalled CloseReason = 3 + CloseReasonUndefined CloseReason = 4 +) + +type Signal struct { + Name string + ID uint32 + ActionKey string + CloseReason CloseReason +} + +// SignalNotify sends notification signals to the channel, such as close or action. +// Note that you can receive other signals, check the ID. +// Does not return until the context is done. +// Do not close the channel until after this function returns. +func SignalNotify(ctx context.Context, ch chan<- Signal) error { + conn, err := dbus.SessionBus() + if err != nil { + return err + } + call := conn.BusObject().Call( + "org.freedesktop.DBus.AddMatch", + 0, + "type='signal',path='"+DbusObjectPath+"',interface='"+DbusInterfacePath+"'") + if call.Err != nil { + return call.Err + } + chinput := make(chan *dbus.Signal) + conn.Signal(chinput) + defer func() { + conn.BusObject().Call( + "org.freedesktop.DBus.RemoveMatch", + 0, + "type='signal',path='"+DbusObjectPath+"',interface='"+DbusInterfacePath+"'") + conn.RemoveSignal(chinput) + close(chinput) + }() + for { + select { + case <-ctx.Done(): + return ctx.Err() + case dsig := <-chinput: + var sig Signal + switch dsig.Name { + case SignalNotificationClosed: + sig = Signal{ + Name: dsig.Name, + ID: dsig.Body[0].(uint32), + CloseReason: CloseReason(dsig.Body[1].(uint32)), + } + case SignalActionInvoked: + sig = Signal{ + Name: dsig.Name, + ID: dsig.Body[0].(uint32), + ActionKey: dsig.Body[1].(string), + } + default: + sig = Signal{Name: dsig.Name} + } + select { + case ch <- sig: + case <-ctx.Done(): + return ctx.Err() + } + } + } +} diff --git a/notify_test.go b/notify_test.go index db908df..2c65b28 100644 --- a/notify_test.go +++ b/notify_test.go @@ -1,6 +1,12 @@ package notify -import "testing" +import ( + "context" + "log" + "sync" + "testing" + "time" +) func TestGetCapabilities(t *testing.T) { c, err := GetCapabilities() @@ -70,3 +76,48 @@ func TestUrgencyNotification(t *testing.T) { t.Fatal(err) } } + +func TestSignalNotify(t *testing.T) { + msg := "Just a test\n\nyou can click things!\n\n(use the -short test switch to skip this)" + ntf := NewNotification("Notification Test", msg) + if testing.Short() { + ntf.Timeout = 500 + log.Print("Using short timeout because of -short") + } else { + ntf.Timeout = 5000 + log.Printf("Using %vms timeout, click the notification or use -short to go faster", ntf.Timeout) + } + ntf.Actions = []string{"my-something", "Something", "default", "Default"} + id, err := ntf.Show() + if err != nil { + t.Fatal(err) + } + + ctx, cancel := context.WithTimeout(context.Background(), 7*time.Second) + defer cancel() + + ch := make(chan Signal, 2) + wg := &sync.WaitGroup{} + + wg.Add(1) + go func() { + defer wg.Done() + for sig := range ch { + log.Printf("Signal: %+v", sig) + if sig.CloseReason == NotClosed { + CloseNotification(id) + } else { + cancel() + break + } + } + }() + + err = SignalNotify(ctx, ch) + if err != nil && err != context.Canceled { + t.Fatal(err) + } + + close(ch) + wg.Wait() +}