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)) }