1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
|
// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
package notify
import (
"bytes"
"fmt"
"image"
"image/color"
"image/png"
"github.com/godbus/dbus/v5"
)
// RawImage is the spec's image-data structure (iiibiiay). Data is RGB byte
// order: 4 bytes per pixel with alpha, 3 without, and rows are RowStride
// bytes apart, which may exceed Width*Channels.
type RawImage struct {
Width int
Height int
RowStride int
HasAlpha bool
BitsPerSample int
Channels int
Data []byte
}
// ImageDataFromHints reads the raw image struct, preferring the spec key then
// the deprecated icon_data, then the underscore alias older libnotify sent.
func ImageDataFromHints(hints map[string]dbus.Variant) (*RawImage, bool) {
for _, key := range []string{"image-data", "icon_data", "image_data"} {
v, ok := hints[key]
if !ok {
continue
}
if r, ok := rawImageFromVariant(v); ok {
return r, true
}
}
return nil, false
}
// ImagePathFromHints reads image-path, a URI, a path, or a theme icon name.
func ImagePathFromHints(hints map[string]dbus.Variant) (string, bool) {
v, ok := hints["image-path"]
if !ok {
return "", false
}
s, ok := v.Value().(string)
if !ok || s == "" {
return "", false
}
return s, true
}
// rawImageFromVariant accepts the []interface{} godbus yields for a struct.
// Each numeric field may arrive as int32 or int depending on the encoder.
func rawImageFromVariant(v dbus.Variant) (*RawImage, bool) {
f, ok := v.Value().([]interface{})
if !ok || len(f) != 7 {
return nil, false
}
r := &RawImage{}
var okW, okH, okS, okC, okD bool
r.Width, okW = asInt(f[0])
r.Height, okH = asInt(f[1])
r.RowStride, okS = asInt(f[2])
r.HasAlpha, _ = f[3].(bool)
r.BitsPerSample, _ = asInt(f[4])
r.Channels, okC = asInt(f[5])
r.Data, okD = f[6].([]byte)
if !okW || !okH || !okS || !okC || !okD {
return nil, false
}
return r, true
}
func asInt(v any) (int, bool) {
switch n := v.(type) {
case int:
return n, true
case int32:
return int(n), true
case int64:
return int(n), true
case uint32:
return int(n), true
}
return 0, false
}
// PNG encodes the raw pixels as a PNG the renderer can load.
func (r *RawImage) PNG() ([]byte, error) {
if r.Width <= 0 || r.Height <= 0 {
return nil, fmt.Errorf("notifyd: image %dx%d", r.Width, r.Height)
}
if r.BitsPerSample != 8 || (r.Channels != 3 && r.Channels != 4) {
return nil, fmt.Errorf("notifyd: image bits=%d channels=%d", r.BitsPerSample, r.Channels)
}
stride := r.RowStride
if stride < r.Width*r.Channels {
stride = r.Width * r.Channels
}
if len(r.Data) < stride*(r.Height-1)+r.Width*r.Channels {
return nil, fmt.Errorf("notifyd: image data short: %d bytes", len(r.Data))
}
img := image.NewRGBA(image.Rect(0, 0, r.Width, r.Height))
for y := 0; y < r.Height; y++ {
row := r.Data[y*stride:]
for x := 0; x < r.Width; x++ {
if r.Channels == 4 {
i := x * 4
img.SetRGBA(x, y, color.RGBA{row[i], row[i+1], row[i+2], row[i+3]})
} else {
i := x * 3
img.SetRGBA(x, y, color.RGBA{row[i], row[i+1], row[i+2], 255})
}
}
}
var buf bytes.Buffer
if err := png.Encode(&buf, img); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
|