2020-01-26 11:04:49 -05:00
|
|
|
package zenity
|
|
|
|
|
2020-01-27 08:44:47 -05:00
|
|
|
import (
|
2023-08-03 12:39:22 -04:00
|
|
|
"encoding/xml"
|
2021-08-13 21:50:04 -04:00
|
|
|
"math/rand"
|
2023-08-03 12:39:22 -04:00
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
2020-01-27 13:11:38 -05:00
|
|
|
"runtime"
|
2023-08-03 12:39:22 -04:00
|
|
|
"strings"
|
2020-01-27 08:44:47 -05:00
|
|
|
"syscall"
|
2023-08-03 12:39:22 -04:00
|
|
|
"text/template"
|
2021-08-13 21:50:04 -04:00
|
|
|
"time"
|
2020-01-27 08:44:47 -05:00
|
|
|
"unsafe"
|
2020-01-30 21:18:43 -05:00
|
|
|
|
2022-06-17 22:45:49 -04:00
|
|
|
"github.com/ncruces/zenity/internal/win"
|
2020-01-30 21:18:43 -05:00
|
|
|
"github.com/ncruces/zenity/internal/zenutil"
|
2020-01-27 08:44:47 -05:00
|
|
|
)
|
|
|
|
|
2021-03-04 07:42:30 -05:00
|
|
|
func notify(text string, opts options) error {
|
2020-01-30 20:31:54 -05:00
|
|
|
if opts.ctx != nil && opts.ctx.Err() != nil {
|
|
|
|
return opts.ctx.Err()
|
|
|
|
}
|
|
|
|
|
2023-08-03 12:39:22 -04:00
|
|
|
file, err := os.CreateTemp("", "*.ps1")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer os.Remove(file.Name())
|
|
|
|
|
|
|
|
data := struct {
|
|
|
|
Title string
|
|
|
|
Text string
|
|
|
|
Icon string
|
|
|
|
}{
|
|
|
|
Title: "zenity",
|
|
|
|
Text: text,
|
|
|
|
}
|
|
|
|
|
|
|
|
if opts.title != nil {
|
|
|
|
data.Title = *opts.title
|
|
|
|
}
|
|
|
|
|
|
|
|
if i, ok := opts.icon.(string); ok {
|
|
|
|
_, err := os.Stat(i)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
data.Icon, err = filepath.Abs(i)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
err = script.Execute(file, data)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = file.Close()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var cmd *exec.Cmd
|
|
|
|
if opts.ctx != nil {
|
|
|
|
cmd = exec.CommandContext(opts.ctx, "PowerShell", "-ExecutionPolicy", "Bypass", "-File", file.Name())
|
|
|
|
} else {
|
|
|
|
cmd = exec.Command("PowerShell", "-ExecutionPolicy", "Bypass", "-File", file.Name())
|
|
|
|
}
|
|
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
|
|
|
err = cmd.Run()
|
|
|
|
|
|
|
|
if opts.ctx != nil && opts.ctx.Err() != nil {
|
|
|
|
return opts.ctx.Err()
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return shellNotify(text, opts)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func shellNotify(text string, opts options) error {
|
2022-06-17 22:45:49 -04:00
|
|
|
var args win.NOTIFYICONDATA
|
2020-01-27 08:44:47 -05:00
|
|
|
args.StructSize = uint32(unsafe.Sizeof(args))
|
2021-08-13 21:50:04 -04:00
|
|
|
args.ID = rand.Uint32()
|
2022-06-22 10:14:52 -04:00
|
|
|
args.Flags = win.NIF_INFO
|
|
|
|
args.State = win.NIS_HIDDEN
|
2020-01-27 08:44:47 -05:00
|
|
|
|
|
|
|
info := syscall.StringToUTF16(text)
|
|
|
|
copy(args.Info[:len(args.Info)-1], info)
|
|
|
|
|
2021-03-03 22:25:45 -05:00
|
|
|
if opts.title != nil {
|
|
|
|
title := syscall.StringToUTF16(*opts.title)
|
|
|
|
copy(args.InfoTitle[:len(args.InfoTitle)-1], title)
|
|
|
|
}
|
2020-01-27 08:44:47 -05:00
|
|
|
|
|
|
|
switch opts.icon {
|
2021-03-04 08:06:49 -05:00
|
|
|
case InfoIcon, QuestionIcon:
|
2022-06-22 10:14:52 -04:00
|
|
|
args.InfoFlags |= win.NIIF_INFO
|
2020-01-27 08:44:47 -05:00
|
|
|
case WarningIcon:
|
2022-06-22 10:14:52 -04:00
|
|
|
args.InfoFlags |= win.NIIF_WARNING
|
2020-01-27 08:44:47 -05:00
|
|
|
case ErrorIcon:
|
2022-06-22 10:14:52 -04:00
|
|
|
args.InfoFlags |= win.NIIF_ERROR
|
2022-05-23 19:12:38 -04:00
|
|
|
default:
|
2022-07-26 11:56:43 -04:00
|
|
|
icon, _ := getIcon(opts.icon)
|
2022-05-23 19:12:38 -04:00
|
|
|
if icon.handle != 0 {
|
|
|
|
defer icon.delete()
|
2022-07-26 09:49:49 -04:00
|
|
|
args.Icon = icon.handle
|
2022-06-22 10:14:52 -04:00
|
|
|
args.Flags |= win.NIF_ICON
|
|
|
|
args.InfoFlags |= win.NIIF_USER
|
2022-05-23 19:12:38 -04:00
|
|
|
}
|
2020-01-27 08:44:47 -05:00
|
|
|
}
|
|
|
|
|
2020-01-27 13:11:38 -05:00
|
|
|
runtime.LockOSThread()
|
|
|
|
defer runtime.UnlockOSThread()
|
|
|
|
|
2022-07-11 19:09:50 -04:00
|
|
|
if !win.ShellNotifyIcon(win.NIM_ADD, &args) {
|
|
|
|
return wtsMessage(text, opts)
|
2020-01-27 08:44:47 -05:00
|
|
|
}
|
|
|
|
|
2022-06-20 11:05:11 -04:00
|
|
|
major, minor, _ := win.RtlGetNtVersionNumbers()
|
2021-08-13 21:50:04 -04:00
|
|
|
// On Windows 7 (6.1) and lower, wait up to 10 seconds to clean up.
|
|
|
|
if major < 6 || major == 6 && minor < 2 {
|
|
|
|
if opts.ctx != nil {
|
|
|
|
select {
|
|
|
|
case <-opts.ctx.Done():
|
|
|
|
case <-time.After(10 * time.Second):
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
time.Sleep(10 * time.Second)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-17 22:45:49 -04:00
|
|
|
win.ShellNotifyIcon(win.NIM_DELETE, &args)
|
2020-01-27 08:44:47 -05:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-01-30 21:18:43 -05:00
|
|
|
func wtsMessage(text string, opts options) error {
|
2022-06-18 11:12:03 -04:00
|
|
|
var flags uint32
|
2020-01-30 21:18:43 -05:00
|
|
|
|
|
|
|
switch opts.icon {
|
|
|
|
case ErrorIcon:
|
2022-06-18 07:37:39 -04:00
|
|
|
flags |= win.MB_ICONERROR
|
2020-01-30 21:18:43 -05:00
|
|
|
case QuestionIcon:
|
2022-06-18 07:37:39 -04:00
|
|
|
flags |= win.MB_ICONQUESTION
|
2020-01-30 21:18:43 -05:00
|
|
|
case WarningIcon:
|
2022-06-18 07:37:39 -04:00
|
|
|
flags |= win.MB_ICONWARNING
|
2020-01-30 21:18:43 -05:00
|
|
|
case InfoIcon:
|
2022-06-18 07:37:39 -04:00
|
|
|
flags |= win.MB_ICONINFORMATION
|
2020-01-30 21:18:43 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
title := opts.title
|
2021-03-03 22:25:45 -05:00
|
|
|
if title == nil {
|
2022-12-14 19:26:34 -05:00
|
|
|
title = ptr("Notification")
|
2020-01-30 21:18:43 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
timeout := zenutil.Timeout
|
|
|
|
if timeout == 0 {
|
|
|
|
timeout = 10
|
|
|
|
}
|
|
|
|
|
|
|
|
ptext := syscall.StringToUTF16(text)
|
2021-03-03 22:25:45 -05:00
|
|
|
ptitle := syscall.StringToUTF16(*title)
|
2020-01-30 21:18:43 -05:00
|
|
|
|
|
|
|
var res uint32
|
2022-06-18 11:12:03 -04:00
|
|
|
return win.WTSSendMessage(
|
|
|
|
win.WTS_CURRENT_SERVER_HANDLE, win.WTS_CURRENT_SESSION,
|
|
|
|
&ptitle[0], 2*len(ptitle), &ptext[0], 2*len(ptext),
|
|
|
|
flags, timeout, &res, false)
|
2020-01-30 21:18:43 -05:00
|
|
|
}
|
2023-08-03 12:39:22 -04:00
|
|
|
|
|
|
|
var script = template.Must(template.New("").Funcs(template.FuncMap{"xml": func(s string) string {
|
|
|
|
var buf strings.Builder
|
|
|
|
xml.EscapeText(&buf, []byte(s))
|
|
|
|
return buf.String()
|
|
|
|
}}).Parse("\xEF\xBB\xBF" + `
|
|
|
|
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime]
|
|
|
|
[Windows.UI.Notifications.ToastNotification, Windows.UI.Notifications, ContentType = WindowsRuntime]
|
|
|
|
[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime]
|
|
|
|
|
|
|
|
$title = @"
|
|
|
|
{{.Title}}
|
|
|
|
"@
|
|
|
|
|
|
|
|
$data = @"
|
|
|
|
<toast activationType="protocol">
|
|
|
|
<visual>
|
|
|
|
<binding template="ToastGeneric">
|
|
|
|
{{- if .Text}}
|
|
|
|
<text><![CDATA[{{.Text}}]]></text>
|
|
|
|
{{- end}}
|
|
|
|
{{- if .Icon}}
|
|
|
|
<image placement="appLogoOverride" src="{{xml .Icon}}" />
|
|
|
|
{{- end}}
|
|
|
|
</binding>
|
|
|
|
</visual>
|
|
|
|
<audio src="ms-winsoundevent:Notification.Default" />
|
|
|
|
</toast>
|
|
|
|
"@
|
|
|
|
|
|
|
|
$xml = New-Object Windows.Data.Xml.Dom.XmlDocument
|
|
|
|
$xml.LoadXml($data)
|
|
|
|
|
|
|
|
$toast = New-Object Windows.UI.Notifications.ToastNotification $xml
|
|
|
|
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($title).Show($toast)
|
|
|
|
`))
|