From 4b5ff500b97cb86a5fbaaf5f3c5f9a004d1334f0 Mon Sep 17 00:00:00 2001 From: Caroline Larimore Date: Thu, 30 Jan 2025 21:47:09 -0800 Subject: Restructure --- cmd/corvid-msg/main.go | 22 ++++++ cmd/corvid/main.go | 10 +++ corvid.go | 85 --------------------- notification.go | 86 ---------------------- server.go | 133 --------------------------------- srv/corvidServer.go | 39 ++++++++++ srv/notifServer.go | 195 +++++++++++++++++++++++++++++++++++++++++++++++++ srv/notification.go | 70 ++++++++++++++++++ srv/server.go | 84 +++++++++++++++++++++ 9 files changed, 420 insertions(+), 304 deletions(-) create mode 100644 cmd/corvid-msg/main.go create mode 100644 cmd/corvid/main.go delete mode 100644 corvid.go delete mode 100644 notification.go delete mode 100644 server.go create mode 100644 srv/corvidServer.go create mode 100644 srv/notifServer.go create mode 100644 srv/notification.go create mode 100644 srv/server.go diff --git a/cmd/corvid-msg/main.go b/cmd/corvid-msg/main.go new file mode 100644 index 0000000..abb87a5 --- /dev/null +++ b/cmd/corvid-msg/main.go @@ -0,0 +1,22 @@ +package main + +import ( + "log" + + "github.com/godbus/dbus/v5" +) + +func main() { + const CORVID_DBUS_OBJECT = "/sh/cxl/Corvid" + const CORVID_DBUS_NAME = "sh.cxl.Corvid" + + conn, err := dbus.SessionBus() + if err != nil { + log.Fatal(err) + } + + call := conn.Object(CORVID_DBUS_NAME, CORVID_DBUS_OBJECT).Call(CORVID_DBUS_NAME+".Test", 0, uint32(13)) + if call.Err != nil { + log.Fatal(call.Err) + } +} diff --git a/cmd/corvid/main.go b/cmd/corvid/main.go new file mode 100644 index 0000000..9064ad6 --- /dev/null +++ b/cmd/corvid/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "github.com/CartConnoisseur/corvid/srv" +) + +func main() { + srv.Start() + select {} +} diff --git a/corvid.go b/corvid.go deleted file mode 100644 index 267f4e5..0000000 --- a/corvid.go +++ /dev/null @@ -1,85 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "log" - "slices" - "sync" - - "github.com/godbus/dbus/v5" -) - -const DEFAULT_EXPIRATION = 5000 -const SORT_DIRECTION = 1 // 1 = newest first, -1 = oldest first -const DBUS_OBJECT = "/org/freedesktop/Notifications" -const DBUS_NAME = "org.freedesktop.Notifications" - -var conn *dbus.Conn - -type notificationStack = struct { - mutex *sync.Mutex - notifications map[uint32]notification - nextId uint32 -} - -var notifications = notificationStack{ - mutex: &sync.Mutex{}, - notifications: make(map[uint32]notification), - nextId: 1, -} - -func output() { - arr := make([]notification, len(notifications.notifications)) - - i := 0 - for _, notification := range notifications.notifications { - arr[i] = notification - i++ - } - - slices.SortFunc(arr, func(a, b notification) int { - if a.Timestamp > b.Timestamp { - return SORT_DIRECTION - } else if a.Timestamp < b.Timestamp { - return -SORT_DIRECTION - } else { - if a.Id > b.Id { - return SORT_DIRECTION - } else if a.Id < b.Id { - return -SORT_DIRECTION - } - } - - return 0 - }) - - j, err := json.Marshal(arr) - if err != nil { - log.Fatalln(err) - } - - fmt.Println(string(j)) -} - -func main() { - var err error - conn, err = dbus.SessionBus() - if err != nil { - log.Fatal(err) - } - - conn.Export(server{}, DBUS_OBJECT, DBUS_NAME) - - reply, err := conn.RequestName(DBUS_NAME, dbus.NameFlagReplaceExisting|dbus.NameFlagDoNotQueue) - if err != nil { - log.Fatal(err) - } - - if reply != dbus.RequestNameReplyPrimaryOwner { - log.Fatalf("'%s' already taken", DBUS_NAME) - } - - log.Print("connected to dbus") - select {} -} diff --git a/notification.go b/notification.go deleted file mode 100644 index 2d5625d..0000000 --- a/notification.go +++ /dev/null @@ -1,86 +0,0 @@ -package main - -import ( - "encoding/json" - "log" - "os" - "time" - - "github.com/godbus/dbus/v5" -) - -type hint struct { - dbus.Variant -} - -func (h hint) MarshalJSON() ([]byte, error) { - //TODO: find a better way lol - switch h.Signature().String()[0] { - case 'y': - return json.Marshal(h.Value().(uint8)) - case 'b': - return json.Marshal(h.Value().(bool)) - case 'n': - return json.Marshal(h.Value().(int16)) - case 'q': - return json.Marshal(h.Value().(uint16)) - case 'i': - return json.Marshal(h.Value().(int32)) - case 'u': - return json.Marshal(h.Value().(uint32)) - case 'x': - return json.Marshal(h.Value().(int64)) - case 't': - return json.Marshal(h.Value().(uint64)) - case 'd': - return json.Marshal(h.Value().(float64)) - case 's': - return json.Marshal(h.Value().(string)) - default: - panic("Impossible type") - } -} - -type closeReason uint32 - -const ( - CloseReasonExpire closeReason = 1 - CloseReasonDismissed = iota - CloseReasonClosed = iota - CloseReasonOther = iota -) - -type notification struct { - Id uint32 `json:"id"` - AppName string `json:"app_name"` - AppIcon string `json:"app_icon"` - Summary string `json:"summary"` - Body string `json:"body"` - Actions map[string]string `json:"actions"` - Hints map[string]hint `json:"hints"` - Timestamp int64 `json:"timestamp"` - Expiration int32 `json:"expiration"` - Image string `json:"image"` - timer *time.Timer -} - -func (n notification) close(reason closeReason) { - notifications.mutex.Lock() - defer notifications.mutex.Unlock() - - if n.timer != nil { - n.timer.Stop() - } - - if n.Image != "" { - os.Remove(n.Image) - } - - delete(notifications.notifications, n.Id) - output() - - err := conn.Emit(DBUS_OBJECT, DBUS_NAME+".NotificationClosed", n.Id, reason) - if err != nil { - log.Print(err) - } -} diff --git a/server.go b/server.go deleted file mode 100644 index 9b94f70..0000000 --- a/server.go +++ /dev/null @@ -1,133 +0,0 @@ -package main - -import ( - "image" - "image/png" - "log" - "os" - "strings" - "time" - - "github.com/godbus/dbus/v5" -) - -type server struct{} - -func (s server) GetCapabilities() (capabilities []string, e *dbus.Error) { - // log.Print("GetCapabilities called") - return []string{ - "body", - "actions", - }, nil -} - -func (s server) GetServerInformation() (name, vendor, version, specVersion string, e *dbus.Error) { - // log.Print("GetServerInformation called") - return "corvid", "CartConnoisseur", "0.1.0", "1.2", nil -} - -func (s server) CloseNotification(id uint32) (e *dbus.Error) { - // log.Printf("CloseNotification called: %d", id) - notification, ok := notifications.notifications[id] - if ok { - notification.close(CloseReasonClosed) - } - - return nil -} - -func (s server) Notify(appName string, replacesId uint32, appIcon string, summary string, body string, actions []string, hints map[string]dbus.Variant, expireTimeout int32) (id uint32, e *dbus.Error) { - // log.Print("Notify called") - notifications.mutex.Lock() - defer notifications.mutex.Unlock() - - if replacesId == 0 { - id = notifications.nextId - notifications.nextId++ - } else { - id = replacesId - } - - actionMap := make(map[string]string) - for i := 0; i < len(actions)-1; i += 2 { - actionMap[actions[i]] = actions[i+1] - } - - hintMap := make(map[string]hint) - img := "" - - for key, value := range hints { - if !value.Signature().Empty() { - if strings.Contains("ybnqiuxtds", string(value.Signature().String()[0])) { - hintMap[key] = hint{Variant: value} - } else if key == "image-data" { - raw := value.Value().([]interface{}) - - var i image.Image - if raw[3].(bool) { - i = &image.NRGBA{ - Pix: raw[6].([]uint8), - Stride: int(raw[2].(int32)), - Rect: image.Rect(0, 0, int(raw[0].(int32)), int(raw[1].(int32))), - } - } else { - rgb := raw[6].([]uint8) - rgba := make([]uint8, len(rgb)/3*4) - - for i := 0; i < len(rgb)-1; i += 3 { - rgba[i/3*4] = rgb[i] - rgba[i/3*4+1] = rgb[i+1] - rgba[i/3*4+2] = rgb[i+2] - rgba[i/3*4+3] = 0xff - } - - i = &image.NRGBA{ - Pix: rgba, - Stride: int(raw[2].(int32)), - Rect: image.Rect(0, 0, int(raw[0].(int32)), int(raw[1].(int32))), - } - } - _ = i - - f, err := os.CreateTemp(os.TempDir(), "corvid-*.png") - if err != nil { - log.Fatal(err) - } - defer f.Close() - - png.Encode(f, i) - - img = f.Name() - } - } - } - - if expireTimeout == -1 { - expireTimeout = DEFAULT_EXPIRATION - } - - notification := notification{ - Id: id, - AppName: appName, - AppIcon: appIcon, - Summary: summary, - Body: body, - Actions: actionMap, - Hints: hintMap, - Timestamp: time.Now().Unix(), - Expiration: expireTimeout, - Image: img, - timer: nil, - } - - if expireTimeout != 0 { - notification.timer = time.AfterFunc(time.Duration(expireTimeout)*time.Millisecond, func() { - notification.close(CloseReasonExpire) - }) - } - - notifications.notifications[id] = notification - output() - - return id, nil -} diff --git a/srv/corvidServer.go b/srv/corvidServer.go new file mode 100644 index 0000000..4a0556d --- /dev/null +++ b/srv/corvidServer.go @@ -0,0 +1,39 @@ +package srv + +import ( + "log" + + "github.com/godbus/dbus/v5" +) + +type corvidServer server + +func (s corvidServer) Test(param uint32) (e *dbus.Error) { + log.Printf("Test called %d", param) + return nil +} + +// func (s corvidServer) GetCapabilities() (e *dbus.Error) { +// log.Print("GetCapabilities called") +// return nil +// } + +// func (s corvidServer) GetServerInformation() (name, vendor, version, specVersion string, e *dbus.Error) { +// // log.Print("GetServerInformation called") +// return "corvid", "CartConnoisseur", "0.1.0", "1.2", nil +// } + +// func (s corvidServer) CloseNotification(id uint32) (e *dbus.Error) { +// // log.Printf("CloseNotification called: %d", id) +// notification, ok := notifications.notifications[id] +// if ok { +// notification.close(CloseReasonClosed) +// } + +// return nil +// } + +// func (s corvidServer) Notify(appName string, replacesId uint32, appIcon string, summary string, body string, actions []string, hints map[string]dbus.Variant, expireTimeout int32) (id uint32, e *dbus.Error) { +// // log.Print("Notify called") + +// } diff --git a/srv/notifServer.go b/srv/notifServer.go new file mode 100644 index 0000000..00038cc --- /dev/null +++ b/srv/notifServer.go @@ -0,0 +1,195 @@ +package srv + +import ( + "encoding/json" + "fmt" + "image" + "image/png" + "log" + "os" + "slices" + "strings" + "time" + + "github.com/godbus/dbus/v5" +) + +type notifServer struct { + notifications *notificationStack + server +} + +func (s notifServer) GetCapabilities() (capabilities []string, e *dbus.Error) { + // log.Print("GetCapabilities called") + return []string{ + "body", + "actions", + }, nil +} + +func (s notifServer) GetServerInformation() (name, vendor, version, specVersion string, e *dbus.Error) { + // log.Print("GetServerInformation called") + return "corvid", "CartConnoisseur", "0.1.0", "1.2", nil +} + +func (s notifServer) CloseNotification(id uint32) (e *dbus.Error) { + // log.Printf("CloseNotification called: %d", id) + s.close(id, CloseReasonClosed) + return nil +} + +func (s notifServer) Notify(appName string, replacesId uint32, appIcon string, summary string, body string, actions []string, hints map[string]dbus.Variant, expireTimeout int32) (id uint32, e *dbus.Error) { + // log.Print("Notify called") + s.notifications.mutex.Lock() + defer s.notifications.mutex.Unlock() + + if replacesId == 0 { + id = s.notifications.nextId + s.notifications.nextId++ + } else { + id = replacesId + } + + actionMap := make(map[string]string) + for i := 0; i < len(actions)-1; i += 2 { + actionMap[actions[i]] = actions[i+1] + } + + hintMap := make(map[string]hint) + img := "" + + for key, value := range hints { + if !value.Signature().Empty() { + if strings.Contains("ybnqiuxtds", string(value.Signature().String()[0])) { + hintMap[key] = hint{Variant: value} + } else if key == "image-data" { + raw := value.Value().([]interface{}) + + var i image.Image + if raw[3].(bool) { + i = &image.NRGBA{ + Pix: raw[6].([]uint8), + Stride: int(raw[2].(int32)), + Rect: image.Rect(0, 0, int(raw[0].(int32)), int(raw[1].(int32))), + } + } else { + rgb := raw[6].([]uint8) + rgba := make([]uint8, len(rgb)/3*4) + + for i := 0; i < len(rgb)-1; i += 3 { + rgba[i/3*4] = rgb[i] + rgba[i/3*4+1] = rgb[i+1] + rgba[i/3*4+2] = rgb[i+2] + rgba[i/3*4+3] = 0xff + } + + i = &image.NRGBA{ + Pix: rgba, + Stride: int(raw[2].(int32)), + Rect: image.Rect(0, 0, int(raw[0].(int32)), int(raw[1].(int32))), + } + } + _ = i + + f, err := os.CreateTemp(os.TempDir(), "corvid-*.png") + if err != nil { + log.Fatal(err) + } + defer f.Close() + + png.Encode(f, i) + + img = f.Name() + } + } + } + + if expireTimeout == -1 { + expireTimeout = DEFAULT_EXPIRATION + } + + notification := notification{ + Id: id, + AppName: appName, + AppIcon: appIcon, + Summary: summary, + Body: body, + Actions: actionMap, + Hints: hintMap, + Timestamp: time.Now().Unix(), + Expiration: expireTimeout, + Image: img, + timer: nil, + } + + if expireTimeout != 0 { + notification.timer = time.AfterFunc(time.Duration(expireTimeout)*time.Millisecond, func() { + s.close(notification.Id, CloseReasonExpire) + }) + } + + s.notifications.notifications[id] = notification + s.output() + + return id, nil +} + +func (s notifServer) close(id uint32, reason closeReason) { + s.notifications.mutex.Lock() + defer s.notifications.mutex.Unlock() + + n, ok := s.notifications.notifications[id] + if !ok { + return + } + + if n.timer != nil { + n.timer.Stop() + } + + if n.Image != "" { + os.Remove(n.Image) + } + + delete(s.notifications.notifications, n.Id) + s.output() + + err := s.conn.Emit(s.object, s.name+".NotificationClosed", n.Id, reason) + if err != nil { + log.Print(err) + } +} + +// TODO: relocate to cmd/corvid +func (s notifServer) output() { + arr := make([]notification, len(s.notifications.notifications)) + + i := 0 + for _, notification := range s.notifications.notifications { + arr[i] = notification + i++ + } + + slices.SortFunc(arr, func(a, b notification) int { + if a.Timestamp > b.Timestamp { + return SORT_DIRECTION + } else if a.Timestamp < b.Timestamp { + return -SORT_DIRECTION + } else { + if a.Id > b.Id { + return SORT_DIRECTION + } else if a.Id < b.Id { + return -SORT_DIRECTION + } + } + + return 0 + }) + + j, err := json.Marshal(arr) + if err != nil { + log.Fatalln(err) + } + + fmt.Println(string(j)) +} diff --git a/srv/notification.go b/srv/notification.go new file mode 100644 index 0000000..dcea9d9 --- /dev/null +++ b/srv/notification.go @@ -0,0 +1,70 @@ +package srv + +import ( + "encoding/json" + "sync" + "time" + + "github.com/godbus/dbus/v5" +) + +type hint struct { + dbus.Variant +} + +func (h hint) MarshalJSON() ([]byte, error) { + //TODO: find a better way lol + switch h.Signature().String()[0] { + case 'y': + return json.Marshal(h.Value().(uint8)) + case 'b': + return json.Marshal(h.Value().(bool)) + case 'n': + return json.Marshal(h.Value().(int16)) + case 'q': + return json.Marshal(h.Value().(uint16)) + case 'i': + return json.Marshal(h.Value().(int32)) + case 'u': + return json.Marshal(h.Value().(uint32)) + case 'x': + return json.Marshal(h.Value().(int64)) + case 't': + return json.Marshal(h.Value().(uint64)) + case 'd': + return json.Marshal(h.Value().(float64)) + case 's': + return json.Marshal(h.Value().(string)) + default: + panic("Impossible type") + } +} + +type closeReason uint32 + +const ( + CloseReasonExpire closeReason = 1 + CloseReasonDismissed = iota + CloseReasonClosed = iota + CloseReasonOther = iota +) + +type notification struct { + Id uint32 `json:"id"` + AppName string `json:"app_name"` + AppIcon string `json:"app_icon"` + Summary string `json:"summary"` + Body string `json:"body"` + Actions map[string]string `json:"actions"` + Hints map[string]hint `json:"hints"` + Timestamp int64 `json:"timestamp"` + Expiration int32 `json:"expiration"` + Image string `json:"image"` + timer *time.Timer +} + +type notificationStack = struct { + mutex *sync.Mutex + notifications map[uint32]notification + nextId uint32 +} diff --git a/srv/server.go b/srv/server.go new file mode 100644 index 0000000..673081b --- /dev/null +++ b/srv/server.go @@ -0,0 +1,84 @@ +package srv + +import ( + "fmt" + "log" + "sync" + + "github.com/godbus/dbus/v5" +) + +const DEFAULT_EXPIRATION = 5000 +const SORT_DIRECTION = 1 // 1 = newest first, -1 = oldest first + +type server = struct { + conn *dbus.Conn + object dbus.ObjectPath + name string +} + +func Start() { + const NOTIF_DBUS_OBJECT = "/org/freedesktop/Notifications" + const NOTIF_DBUS_NAME = "org.freedesktop.Notifications" + const CORVID_DBUS_OBJECT = "/sh/cxl/Corvid" + const CORVID_DBUS_NAME = "sh.cxl.Corvid" + + notifications := notificationStack{ + mutex: &sync.Mutex{}, + notifications: make(map[uint32]notification), + nextId: 1, + } + + conn, err := dbus.SessionBus() + if err != nil { + log.Fatal(err) + } + + err = startDBusServer( + conn, + corvidServer{ + conn: conn, + object: CORVID_DBUS_OBJECT, + name: CORVID_DBUS_NAME, + }, + CORVID_DBUS_OBJECT, + CORVID_DBUS_NAME, + ) + if err != nil { + log.Fatal(err) + } + + err = startDBusServer( + conn, + notifServer{ + notifications: ¬ifications, + server: server{ + conn: conn, + object: NOTIF_DBUS_OBJECT, + name: NOTIF_DBUS_NAME, + }, + }, + NOTIF_DBUS_OBJECT, + NOTIF_DBUS_NAME, + ) + if err != nil { + log.Fatal(err) + } +} + +func startDBusServer(conn *dbus.Conn, v interface{}, object dbus.ObjectPath, name string) error { + conn.Export(v, object, name) + + reply, err := conn.RequestName(name, dbus.NameFlagReplaceExisting|dbus.NameFlagDoNotQueue) + if err != nil { + return err + } + + if reply != dbus.RequestNameReplyPrimaryOwner { + return fmt.Errorf("'%s' already taken", name) + } + + log.Printf("connected to dbus as %s @ %s", name, object) + + return nil +} -- cgit v1.2.3