safing-portmaster/cmds/notifier/wintoast/wintoast.go

217 lines
6.4 KiB
Go

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