diff options
Diffstat (limited to 'srv')
| -rw-r--r-- | srv/corvidServer.go | 39 | ||||
| -rw-r--r-- | srv/notifServer.go | 195 | ||||
| -rw-r--r-- | srv/notification.go | 70 | ||||
| -rw-r--r-- | srv/server.go | 84 |
4 files changed, 388 insertions, 0 deletions
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 +} |