Migrate notifier from portmaster-ui to cmds/notifier, remove some duplicated code, move assets to assets/data and add a small go package in assets to allow embedding icons
This commit is contained in:
90
cmds/notifier/wintoast/notification_builder.go
Normal file
90
cmds/notifier/wintoast/notification_builder.go
Normal file
@@ -0,0 +1,90 @@
|
||||
//go:build windows
|
||||
|
||||
package wintoast
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type NotificationBuilder struct {
|
||||
templatePointer uintptr
|
||||
lib *WinToast
|
||||
}
|
||||
|
||||
func newNotification(lib *WinToast, title string, message string) (*NotificationBuilder, error) {
|
||||
lib.Lock()
|
||||
defer lib.Unlock()
|
||||
|
||||
titleUTF, _ := windows.UTF16PtrFromString(title)
|
||||
messageUTF, _ := windows.UTF16PtrFromString(message)
|
||||
titleP := unsafe.Pointer(titleUTF)
|
||||
messageP := unsafe.Pointer(messageUTF)
|
||||
|
||||
ptr, _, err := lib.createNotification.Call(uintptr(titleP), uintptr(messageP))
|
||||
if ptr == 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &NotificationBuilder{ptr, lib}, nil
|
||||
}
|
||||
|
||||
func (n *NotificationBuilder) Delete() {
|
||||
if n == nil {
|
||||
return
|
||||
}
|
||||
|
||||
n.lib.Lock()
|
||||
defer n.lib.Unlock()
|
||||
|
||||
_, _, _ = n.lib.deleteNotification.Call(n.templatePointer)
|
||||
}
|
||||
|
||||
func (n *NotificationBuilder) AddButton(text string) error {
|
||||
n.lib.Lock()
|
||||
defer n.lib.Unlock()
|
||||
textUTF, _ := windows.UTF16PtrFromString(text)
|
||||
textP := unsafe.Pointer(textUTF)
|
||||
|
||||
rc, _, err := n.lib.addButton.Call(n.templatePointer, uintptr(textP))
|
||||
if rc != 1 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NotificationBuilder) SetImage(iconPath string) error {
|
||||
n.lib.Lock()
|
||||
defer n.lib.Unlock()
|
||||
pathUTF, _ := windows.UTF16PtrFromString(iconPath)
|
||||
pathP := unsafe.Pointer(pathUTF)
|
||||
|
||||
rc, _, err := n.lib.setImage.Call(n.templatePointer, uintptr(pathP))
|
||||
if rc != 1 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NotificationBuilder) SetSound(option int, path int) error {
|
||||
n.lib.Lock()
|
||||
defer n.lib.Unlock()
|
||||
|
||||
rc, _, err := n.lib.setSound.Call(n.templatePointer, uintptr(option), uintptr(path))
|
||||
if rc != 1 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NotificationBuilder) Show() (int64, error) {
|
||||
n.lib.Lock()
|
||||
defer n.lib.Unlock()
|
||||
|
||||
id, _, err := n.lib.showNotification.Call(n.templatePointer)
|
||||
if int64(id) == -1 {
|
||||
return -1, err
|
||||
}
|
||||
return int64(id), nil
|
||||
}
|
||||
217
cmds/notifier/wintoast/wintoast.go
Normal file
217
cmds/notifier/wintoast/wintoast.go
Normal file
@@ -0,0 +1,217 @@
|
||||
//go:build windows
|
||||
|
||||
package wintoast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tevino/abool"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// WinNotify holds the DLL handle.
|
||||
type WinToast struct {
|
||||
sync.RWMutex
|
||||
|
||||
dll *windows.DLL
|
||||
|
||||
initialized *abool.AtomicBool
|
||||
|
||||
initialize *windows.Proc
|
||||
isInitialized *windows.Proc
|
||||
createNotification *windows.Proc
|
||||
deleteNotification *windows.Proc
|
||||
addButton *windows.Proc
|
||||
setImage *windows.Proc
|
||||
setSound *windows.Proc
|
||||
showNotification *windows.Proc
|
||||
hideNotification *windows.Proc
|
||||
setActivatedCallback *windows.Proc
|
||||
setDismissedCallback *windows.Proc
|
||||
setFailedCallback *windows.Proc
|
||||
}
|
||||
|
||||
func New(dllPath string) (*WinToast, error) {
|
||||
if dllPath == "" {
|
||||
return nil, fmt.Errorf("winnotifiy: path to dll not specified")
|
||||
}
|
||||
|
||||
libraryObject := &WinToast{}
|
||||
libraryObject.initialized = abool.New()
|
||||
|
||||
// load dll
|
||||
var err error
|
||||
libraryObject.dll, err = windows.LoadDLL(dllPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("winnotifiy: failed to load notifier dll %w", err)
|
||||
}
|
||||
|
||||
// load functions
|
||||
libraryObject.initialize, err = libraryObject.dll.FindProc("PortmasterToastInitialize")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("winnotifiy: PortmasterToastInitialize not found %w", err)
|
||||
}
|
||||
|
||||
libraryObject.isInitialized, err = libraryObject.dll.FindProc("PortmasterToastIsInitialized")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("winnotifiy: PortmasterToastIsInitialized not found %w", err)
|
||||
}
|
||||
|
||||
libraryObject.createNotification, err = libraryObject.dll.FindProc("PortmasterToastCreateNotification")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("winnotifiy: PortmasterToastCreateNotification not found %w", err)
|
||||
}
|
||||
|
||||
libraryObject.deleteNotification, err = libraryObject.dll.FindProc("PortmasterToastDeleteNotification")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("winnotifiy: PortmasterToastDeleteNotification not found %w", err)
|
||||
}
|
||||
|
||||
libraryObject.addButton, err = libraryObject.dll.FindProc("PortmasterToastAddButton")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("winnotifiy: PortmasterToastAddButton not found %w", err)
|
||||
}
|
||||
|
||||
libraryObject.setImage, err = libraryObject.dll.FindProc("PortmasterToastSetImage")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("winnotifiy: PortmasterToastSetImage not found %w", err)
|
||||
}
|
||||
|
||||
libraryObject.setSound, err = libraryObject.dll.FindProc("PortmasterToastSetSound")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("winnotifiy: PortmasterToastSetSound not found %w", err)
|
||||
}
|
||||
|
||||
libraryObject.showNotification, err = libraryObject.dll.FindProc("PortmasterToastShow")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("winnotifiy: PortmasterToastShow not found %w", err)
|
||||
}
|
||||
|
||||
libraryObject.setActivatedCallback, err = libraryObject.dll.FindProc("PortmasterToastActivatedCallback")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("winnotifiy: PortmasterActivatedCallback not found %w", err)
|
||||
}
|
||||
|
||||
libraryObject.setDismissedCallback, err = libraryObject.dll.FindProc("PortmasterToastDismissedCallback")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("winnotifiy: PortmasterToastDismissedCallback not found %w", err)
|
||||
}
|
||||
|
||||
libraryObject.setFailedCallback, err = libraryObject.dll.FindProc("PortmasterToastFailedCallback")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("winnotifiy: PortmasterToastFailedCallback not found %w", err)
|
||||
}
|
||||
|
||||
libraryObject.hideNotification, err = libraryObject.dll.FindProc("PortmasterToastHide")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("winnotifiy: PortmasterToastHide not found %w", err)
|
||||
}
|
||||
|
||||
return libraryObject, nil
|
||||
}
|
||||
|
||||
func (lib *WinToast) Initialize(appName, aumi, originalShortcutPath string) error {
|
||||
if lib == nil {
|
||||
return fmt.Errorf("wintoast: lib object was nil")
|
||||
}
|
||||
|
||||
lib.Lock()
|
||||
defer lib.Unlock()
|
||||
|
||||
// Initialize all necessary string for the notification meta data
|
||||
appNameUTF, _ := windows.UTF16PtrFromString(appName)
|
||||
aumiUTF, _ := windows.UTF16PtrFromString(aumi)
|
||||
linkUTF, _ := windows.UTF16PtrFromString(originalShortcutPath)
|
||||
|
||||
// They are needed as unsafe pointers
|
||||
appNameP := unsafe.Pointer(appNameUTF)
|
||||
aumiP := unsafe.Pointer(aumiUTF)
|
||||
linkP := unsafe.Pointer(linkUTF)
|
||||
|
||||
// Initialize notifications
|
||||
rc, _, err := lib.initialize.Call(uintptr(appNameP), uintptr(aumiP), uintptr(linkP))
|
||||
if rc != 0 {
|
||||
return fmt.Errorf("wintoast: failed to initialize library rc = %d, %w", rc, err)
|
||||
}
|
||||
|
||||
// Check if if the initialization was successfully
|
||||
rc, _, _ = lib.isInitialized.Call()
|
||||
if rc == 1 {
|
||||
lib.initialized.Set()
|
||||
} else {
|
||||
return fmt.Errorf("wintoast: initialized flag was not set: rc = %d", rc)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lib *WinToast) SetCallbacks(activated func(id int64, actionIndex int32), dismissed func(id int64, reason int32), failed func(id int64, reason int32)) error {
|
||||
if lib == nil {
|
||||
return fmt.Errorf("wintoast: lib object was nil")
|
||||
}
|
||||
|
||||
if lib.initialized.IsNotSet() {
|
||||
return fmt.Errorf("winnotifiy: library not initialized")
|
||||
}
|
||||
|
||||
// Initialize notification activated callback
|
||||
callback := windows.NewCallback(func(id int64, actionIndex int32) uint64 {
|
||||
activated(id, actionIndex)
|
||||
return 0
|
||||
})
|
||||
rc, _, err := lib.setActivatedCallback.Call(callback)
|
||||
if rc != 1 {
|
||||
return fmt.Errorf("winnotifiy: failed to initialize activated callback %w", err)
|
||||
}
|
||||
|
||||
// Initialize notification dismissed callback
|
||||
callback = windows.NewCallback(func(id int64, actionIndex int32) uint64 {
|
||||
dismissed(id, actionIndex)
|
||||
return 0
|
||||
})
|
||||
rc, _, err = lib.setDismissedCallback.Call(callback)
|
||||
if rc != 1 {
|
||||
return fmt.Errorf("winnotifiy: failed to initialize dismissed callback %w", err)
|
||||
}
|
||||
|
||||
// Initialize notification failed callback
|
||||
callback = windows.NewCallback(func(id int64, actionIndex int32) uint64 {
|
||||
failed(id, actionIndex)
|
||||
return 0
|
||||
})
|
||||
rc, _, err = lib.setFailedCallback.Call(callback)
|
||||
if rc != 1 {
|
||||
return fmt.Errorf("winnotifiy: failed to initialize failed callback %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewNotification starts a creation of new notification. NotificationBuilder.Delete should allays be called when done using the object or there will be memory leeks
|
||||
func (lib *WinToast) NewNotification(title string, content string) (*NotificationBuilder, error) {
|
||||
if lib == nil {
|
||||
return nil, fmt.Errorf("wintoast: lib object was nil")
|
||||
}
|
||||
return newNotification(lib, title, content)
|
||||
}
|
||||
|
||||
// HideNotification hides notification
|
||||
func (lib *WinToast) HideNotification(id int64) error {
|
||||
if lib == nil {
|
||||
return fmt.Errorf("wintoast: lib object was nil")
|
||||
}
|
||||
|
||||
lib.Lock()
|
||||
defer lib.Unlock()
|
||||
|
||||
rc, _, _ := lib.hideNotification.Call(uintptr(id))
|
||||
|
||||
if rc != 1 {
|
||||
return fmt.Errorf("wintoast: failed to hide notification %d", id)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user