-
Notifications
You must be signed in to change notification settings - Fork 102
Expand file tree
/
Copy pathnotify_unix.go
More file actions
181 lines (150 loc) · 3.78 KB
/
Copy pathnotify_unix.go
File metadata and controls
181 lines (150 loc) · 3.78 KB
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
//go:build (linux || freebsd || netbsd || openbsd || illumos) && !nodbus
package beeep
import (
"bytes"
"fmt"
"image"
"image/draw"
"image/png"
"io"
"log"
"os"
"os/exec"
"strconv"
"github.com/esiqveland/notify"
"github.com/godbus/dbus/v5"
)
// Notify sends desktop notification.
// The icon can be string with a path to png file or png []byte data. Stock icon names can also be used where supported.
//
// On Linux it tries to send notification via D-Bus, and it will fall back to `notify-send`.
//
// On macOS, this will first try `terminal-notifier` and will fall back to AppleScript with `osascript`.
//
// On Windows 10/11 it will use Windows Runtime COM API and will fall back to PowerShell. Windows 7 will use win32 API.
//
// On the Web it uses the Notification API, in Firefox it just works, in Chrome you must call it from some "user gesture"
// like `onclick`, and you must use TLS certificate, it doesn't work with plain http.
func Notify(title, message string, icon any) error {
return notify1(title, message, icon, false)
}
func notify1(title, message string, ico any, urgent bool) error {
var isString, isBytes bool
switch ico.(type) {
case string:
isString = true
case []byte:
isBytes = true
default:
return fmt.Errorf("unsupported argument: %T", ico)
}
var icon string
if isString {
icon = ico.(string)
if _, err := os.Stat(icon); err == nil {
icon = pathAbs(icon)
}
}
cmd1 := func() error {
cmd, err := exec.LookPath("notify-send")
if err != nil {
return err
}
if isBytes {
tmp, err := bytesToFilename(ico.([]byte))
if err != nil {
return err
}
defer os.Remove(tmp)
icon = tmp
}
args := []string{title, message, "-a", AppName, "-i", icon, "-t", strconv.Itoa(int(timeout.Milliseconds())), "-u"}
if urgent {
args = append(args, "critical")
} else {
args = append(args, "normal")
}
c := exec.Command(cmd, args...)
return c.Run()
}
cmd2 := func() error {
cmd, err := exec.LookPath("kdialog")
if err != nil {
return err
}
if isBytes {
tmp, err := bytesToFilename(ico.([]byte))
if err != nil {
return err
}
defer os.Remove(tmp)
icon = tmp
}
args := []string{"--title", title, "--passivepopup", message, strconv.Itoa(int(timeout.Seconds())), "--icon", icon}
c := exec.Command(cmd, args...)
return c.Run()
}
dbus1 := func() error {
conn, err := dbus.SessionBus()
if err != nil {
return err
}
n := notify.Notification{
AppName: AppName,
AppIcon: icon,
Summary: title,
Body: message,
ExpireTimeout: timeout,
}
n.Hints = map[string]dbus.Variant{}
if urgent {
soundHint := notify.HintSoundWithName("bell")
n.Hints[soundHint.ID] = soundHint.Variant
n.SetUrgency(notify.UrgencyCritical)
} else {
n.SetUrgency(notify.UrgencyNormal)
}
if isBytes {
rgba, err := bytesToRGBA(ico.([]byte))
if err != nil {
return err
}
imageHint := notify.HintImageDataRGBA(rgba)
n.Hints[imageHint.ID] = imageHint.Variant
}
notifier, err := notify.New(conn, notify.WithLogger(log.New(io.Discard, "", log.Flags())))
if err != nil {
return err
}
defer notifier.Close()
_, err = notifier.SendNotification(n)
if err != nil {
return err
}
return nil
}
err := dbus1()
if err != nil {
err1 := cmd1()
if err1 != nil {
err2 := cmd2()
if err2 != nil {
return fmt.Errorf("beeep: dbus: %w; notify-send: %w; kdialog: %w", err, err1, err2)
}
}
}
return nil
}
func bytesToRGBA(data []byte) (*image.RGBA, error) {
i, err := png.Decode(bytes.NewReader(data))
if err != nil {
return nil, err
}
if img, ok := i.(*image.RGBA); ok {
return img, nil
}
b := i.Bounds()
img := image.NewRGBA(image.Rect(0, 0, b.Dx(), b.Dy()))
draw.Draw(img, img.Bounds(), i, b.Min, draw.Src)
return img, nil
}