mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 14:45:49 +01:00
reorg platform impl
This commit is contained in:
parent
b1db2bba12
commit
88f13369b6
4 changed files with 348 additions and 232 deletions
|
|
@ -13,12 +13,38 @@
|
|||
package notifications
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type notificationImpl interface {
|
||||
// Lifecycle methods
|
||||
Startup(ctx context.Context) error
|
||||
Shutdown() error
|
||||
|
||||
// Core notification methods
|
||||
RequestNotificationAuthorization() (bool, error)
|
||||
CheckNotificationAuthorization() (bool, error)
|
||||
SendNotification(options NotificationOptions) error
|
||||
SendNotificationWithActions(options NotificationOptions) error
|
||||
|
||||
// Category management
|
||||
RegisterNotificationCategory(category NotificationCategory) error
|
||||
RemoveNotificationCategory(categoryID string) error
|
||||
|
||||
// Notification management
|
||||
RemoveAllPendingNotifications() error
|
||||
RemovePendingNotification(identifier string) error
|
||||
RemoveAllDeliveredNotifications() error
|
||||
RemoveDeliveredNotification(identifier string) error
|
||||
RemoveNotification(identifier string) error
|
||||
}
|
||||
|
||||
// Service represents the notifications service
|
||||
type Service struct {
|
||||
impl notificationImpl
|
||||
|
||||
// notificationResponseCallback is called when a notification result is received.
|
||||
// Only one callback can be assigned at a time.
|
||||
notificationResultCallback func(result NotificationResult)
|
||||
|
|
@ -83,12 +109,6 @@ func (ns *Service) ServiceName() string {
|
|||
return "github.com/wailsapp/wails/v3/services/notifications"
|
||||
}
|
||||
|
||||
func getNotificationService() *Service {
|
||||
notificationServiceLock.RLock()
|
||||
defer notificationServiceLock.RUnlock()
|
||||
return NotificationService
|
||||
}
|
||||
|
||||
// OnNotificationResponse registers a callback function that will be called when
|
||||
// a notification response is received from the user.
|
||||
//
|
||||
|
|
@ -112,6 +132,73 @@ func (ns *Service) handleNotificationResult(result NotificationResult) {
|
|||
}
|
||||
}
|
||||
|
||||
// ServiceStartup is called when the service is loaded
|
||||
func (ns *Service) ServiceStartup(ctx context.Context, options interface{}) error {
|
||||
return ns.impl.Startup(ctx)
|
||||
}
|
||||
|
||||
// ServiceShutdown is called when the service is unloaded
|
||||
func (ns *Service) ServiceShutdown() error {
|
||||
return ns.impl.Shutdown()
|
||||
}
|
||||
|
||||
// Public methods that delegate to the implementation
|
||||
func (ns *Service) RequestNotificationAuthorization() (bool, error) {
|
||||
return ns.impl.RequestNotificationAuthorization()
|
||||
}
|
||||
|
||||
func (ns *Service) CheckNotificationAuthorization() (bool, error) {
|
||||
return ns.impl.CheckNotificationAuthorization()
|
||||
}
|
||||
|
||||
func (ns *Service) SendNotification(options NotificationOptions) error {
|
||||
if err := validateNotificationOptions(options); err != nil {
|
||||
return err
|
||||
}
|
||||
return ns.impl.SendNotification(options)
|
||||
}
|
||||
|
||||
func (ns *Service) SendNotificationWithActions(options NotificationOptions) error {
|
||||
if err := validateNotificationOptions(options); err != nil {
|
||||
return err
|
||||
}
|
||||
return ns.impl.SendNotificationWithActions(options)
|
||||
}
|
||||
|
||||
func (ns *Service) RegisterNotificationCategory(category NotificationCategory) error {
|
||||
return ns.impl.RegisterNotificationCategory(category)
|
||||
}
|
||||
|
||||
func (ns *Service) RemoveNotificationCategory(categoryID string) error {
|
||||
return ns.impl.RemoveNotificationCategory(categoryID)
|
||||
}
|
||||
|
||||
func (ns *Service) RemoveAllPendingNotifications() error {
|
||||
return ns.impl.RemoveAllPendingNotifications()
|
||||
}
|
||||
|
||||
func (ns *Service) RemovePendingNotification(identifier string) error {
|
||||
return ns.impl.RemovePendingNotification(identifier)
|
||||
}
|
||||
|
||||
func (ns *Service) RemoveAllDeliveredNotifications() error {
|
||||
return ns.impl.RemoveAllDeliveredNotifications()
|
||||
}
|
||||
|
||||
func (ns *Service) RemoveDeliveredNotification(identifier string) error {
|
||||
return ns.impl.RemoveDeliveredNotification(identifier)
|
||||
}
|
||||
|
||||
func (ns *Service) RemoveNotification(identifier string) error {
|
||||
return ns.impl.RemoveNotification(identifier)
|
||||
}
|
||||
|
||||
func getNotificationService() *Service {
|
||||
notificationServiceLock.RLock()
|
||||
defer notificationServiceLock.RUnlock()
|
||||
return NotificationService
|
||||
}
|
||||
|
||||
// validateNotificationOptions validates an ID and Title are provided for notifications
|
||||
func validateNotificationOptions(options NotificationOptions) error {
|
||||
if options.ID == "" {
|
||||
|
|
|
|||
|
|
@ -17,17 +17,17 @@ import (
|
|||
"unsafe"
|
||||
)
|
||||
|
||||
type darwinNotifier struct {
|
||||
channels map[int]chan notificationChannel
|
||||
channelsLock sync.Mutex
|
||||
nextChannelID int
|
||||
}
|
||||
|
||||
type notificationChannel struct {
|
||||
Success bool
|
||||
Error error
|
||||
}
|
||||
|
||||
var (
|
||||
notificationChannels = make(map[int]chan notificationChannel)
|
||||
notificationChannelsLock sync.Mutex
|
||||
nextChannelID int
|
||||
)
|
||||
|
||||
const AppleDefaultActionIdentifier = "com.apple.UNNotificationDefaultActionIdentifier"
|
||||
|
||||
// Creates a new Notifications Service.
|
||||
|
|
@ -43,25 +43,38 @@ func New() *Service {
|
|||
" 3. Run the signed .app bundle")
|
||||
}
|
||||
|
||||
if NotificationService == nil {
|
||||
NotificationService = &Service{}
|
||||
impl := &darwinNotifier{
|
||||
channels: make(map[int]chan notificationChannel),
|
||||
nextChannelID: 0,
|
||||
}
|
||||
|
||||
NotificationService = &Service{
|
||||
impl: impl,
|
||||
}
|
||||
})
|
||||
|
||||
return NotificationService
|
||||
}
|
||||
|
||||
func (dn *darwinNotifier) Startup(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dn *darwinNotifier) Shutdown() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func CheckBundleIdentifier() bool {
|
||||
return bool(C.checkBundleIdentifier())
|
||||
}
|
||||
|
||||
// RequestNotificationAuthorization requests permission for notifications.
|
||||
// Default timeout is 15 minutes
|
||||
func (ns *Service) RequestNotificationAuthorization() (bool, error) {
|
||||
func (dn *darwinNotifier) RequestNotificationAuthorization() (bool, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*900)
|
||||
defer cancel()
|
||||
|
||||
id, resultCh := registerChannel()
|
||||
id, resultCh := dn.registerChannel()
|
||||
|
||||
C.requestNotificationAuthorization(C.int(id))
|
||||
|
||||
|
|
@ -69,17 +82,17 @@ func (ns *Service) RequestNotificationAuthorization() (bool, error) {
|
|||
case result := <-resultCh:
|
||||
return result.Success, result.Error
|
||||
case <-ctx.Done():
|
||||
cleanupChannel(id)
|
||||
dn.cleanupChannel(id)
|
||||
return false, fmt.Errorf("notification authorization timed out after 15 minutes: %w", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
// CheckNotificationAuthorization checks current notification permission status.
|
||||
func (ns *Service) CheckNotificationAuthorization() (bool, error) {
|
||||
func (dn *darwinNotifier) CheckNotificationAuthorization() (bool, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
id, resultCh := registerChannel()
|
||||
id, resultCh := dn.registerChannel()
|
||||
|
||||
C.checkNotificationAuthorization(C.int(id))
|
||||
|
||||
|
|
@ -87,13 +100,13 @@ func (ns *Service) CheckNotificationAuthorization() (bool, error) {
|
|||
case result := <-resultCh:
|
||||
return result.Success, result.Error
|
||||
case <-ctx.Done():
|
||||
cleanupChannel(id)
|
||||
dn.cleanupChannel(id)
|
||||
return false, fmt.Errorf("notification authorization timed out after 15s: %w", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
// SendNotification sends a basic notification with a unique identifier, title, subtitle, and body.
|
||||
func (ns *Service) SendNotification(options NotificationOptions) error {
|
||||
func (dn *darwinNotifier) SendNotification(options NotificationOptions) error {
|
||||
if err := validateNotificationOptions(options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -101,7 +114,7 @@ func (ns *Service) SendNotification(options NotificationOptions) error {
|
|||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
id, resultCh := registerChannel()
|
||||
id, resultCh := dn.registerChannel()
|
||||
|
||||
cIdentifier := C.CString(options.ID)
|
||||
cTitle := C.CString(options.Title)
|
||||
|
|
@ -134,7 +147,7 @@ func (ns *Service) SendNotification(options NotificationOptions) error {
|
|||
}
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
cleanupChannel(id)
|
||||
dn.cleanupChannel(id)
|
||||
return fmt.Errorf("sending notification timed out: %w", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
|
@ -142,7 +155,7 @@ func (ns *Service) SendNotification(options NotificationOptions) error {
|
|||
// SendNotificationWithActions sends a notification with additional actions and inputs.
|
||||
// A NotificationCategory must be registered with RegisterNotificationCategory first. The `CategoryID` must match the registered category.
|
||||
// If a NotificationCategory is not registered a basic notification will be sent.
|
||||
func (ns *Service) SendNotificationWithActions(options NotificationOptions) error {
|
||||
func (dn *darwinNotifier) SendNotificationWithActions(options NotificationOptions) error {
|
||||
if err := validateNotificationOptions(options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -150,7 +163,7 @@ func (ns *Service) SendNotificationWithActions(options NotificationOptions) erro
|
|||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
id, resultCh := registerChannel()
|
||||
id, resultCh := dn.registerChannel()
|
||||
|
||||
cIdentifier := C.CString(options.ID)
|
||||
cTitle := C.CString(options.Title)
|
||||
|
|
@ -184,18 +197,18 @@ func (ns *Service) SendNotificationWithActions(options NotificationOptions) erro
|
|||
}
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
cleanupChannel(id)
|
||||
dn.cleanupChannel(id)
|
||||
return fmt.Errorf("sending notification timed out: %w", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterNotificationCategory registers a new NotificationCategory to be used with SendNotificationWithActions.
|
||||
// Registering a category with the same name as a previously registered NotificationCategory will override it.
|
||||
func (ns *Service) RegisterNotificationCategory(category NotificationCategory) error {
|
||||
func (dn *darwinNotifier) RegisterNotificationCategory(category NotificationCategory) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
id, resultCh := registerChannel()
|
||||
id, resultCh := dn.registerChannel()
|
||||
|
||||
cCategoryID := C.CString(category.ID)
|
||||
defer C.free(unsafe.Pointer(cCategoryID))
|
||||
|
|
@ -228,17 +241,17 @@ func (ns *Service) RegisterNotificationCategory(category NotificationCategory) e
|
|||
}
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
cleanupChannel(id)
|
||||
dn.cleanupChannel(id)
|
||||
return fmt.Errorf("category registration timed out: %w", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveNotificationCategory remove a previously registered NotificationCategory.
|
||||
func (ns *Service) RemoveNotificationCategory(categoryId string) error {
|
||||
func (dn *darwinNotifier) RemoveNotificationCategory(categoryId string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
id, resultCh := registerChannel()
|
||||
id, resultCh := dn.registerChannel()
|
||||
|
||||
cCategoryID := C.CString(categoryId)
|
||||
defer C.free(unsafe.Pointer(cCategoryID))
|
||||
|
|
@ -255,19 +268,19 @@ func (ns *Service) RemoveNotificationCategory(categoryId string) error {
|
|||
}
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
cleanupChannel(id)
|
||||
dn.cleanupChannel(id)
|
||||
return fmt.Errorf("category removal timed out: %w", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveAllPendingNotifications removes all pending notifications.
|
||||
func (ns *Service) RemoveAllPendingNotifications() error {
|
||||
func (dn *darwinNotifier) RemoveAllPendingNotifications() error {
|
||||
C.removeAllPendingNotifications()
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemovePendingNotification removes a pending notification matching the unique identifier.
|
||||
func (ns *Service) RemovePendingNotification(identifier string) error {
|
||||
func (dn *darwinNotifier) RemovePendingNotification(identifier string) error {
|
||||
cIdentifier := C.CString(identifier)
|
||||
defer C.free(unsafe.Pointer(cIdentifier))
|
||||
C.removePendingNotification(cIdentifier)
|
||||
|
|
@ -275,13 +288,13 @@ func (ns *Service) RemovePendingNotification(identifier string) error {
|
|||
}
|
||||
|
||||
// RemoveAllDeliveredNotifications removes all delivered notifications.
|
||||
func (ns *Service) RemoveAllDeliveredNotifications() error {
|
||||
func (dn *darwinNotifier) RemoveAllDeliveredNotifications() error {
|
||||
C.removeAllDeliveredNotifications()
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveDeliveredNotification removes a delivered notification matching the unique identifier.
|
||||
func (ns *Service) RemoveDeliveredNotification(identifier string) error {
|
||||
func (dn *darwinNotifier) RemoveDeliveredNotification(identifier string) error {
|
||||
cIdentifier := C.CString(identifier)
|
||||
defer C.free(unsafe.Pointer(cIdentifier))
|
||||
C.removeDeliveredNotification(cIdentifier)
|
||||
|
|
@ -295,13 +308,23 @@ func (ns *Service) RemoveDeliveredNotification(identifier string) error {
|
|||
// RemoveAllDeliveredNotifications
|
||||
// RemoveDeliveredNotification
|
||||
// (Linux-specific)
|
||||
func (ns *Service) RemoveNotification(identifier string) error {
|
||||
func (dn *darwinNotifier) RemoveNotification(identifier string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
//export captureResult
|
||||
func captureResult(channelID C.int, success C.bool, errorMsg *C.char) {
|
||||
resultCh, exists := getChannel(int(channelID))
|
||||
ns := getNotificationService()
|
||||
if ns != nil {
|
||||
return
|
||||
}
|
||||
|
||||
dn, ok := ns.impl.(*darwinNotifier)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
resultCh, exists := dn.getChannel(int(channelID))
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
|
|
@ -361,36 +384,38 @@ func didReceiveNotificationResponse(jsonPayload *C.char, err *C.char) {
|
|||
}
|
||||
}
|
||||
|
||||
func registerChannel() (int, chan notificationChannel) {
|
||||
notificationChannelsLock.Lock()
|
||||
defer notificationChannelsLock.Unlock()
|
||||
// Helper methods
|
||||
|
||||
id := nextChannelID
|
||||
nextChannelID++
|
||||
func (dn *darwinNotifier) registerChannel() (int, chan notificationChannel) {
|
||||
dn.channelsLock.Lock()
|
||||
defer dn.channelsLock.Unlock()
|
||||
|
||||
id := dn.nextChannelID
|
||||
dn.nextChannelID++
|
||||
|
||||
resultCh := make(chan notificationChannel, 1)
|
||||
|
||||
notificationChannels[id] = resultCh
|
||||
dn.channels[id] = resultCh
|
||||
return id, resultCh
|
||||
}
|
||||
|
||||
func getChannel(id int) (chan notificationChannel, bool) {
|
||||
notificationChannelsLock.Lock()
|
||||
defer notificationChannelsLock.Unlock()
|
||||
func (dn *darwinNotifier) getChannel(id int) (chan notificationChannel, bool) {
|
||||
dn.channelsLock.Lock()
|
||||
defer dn.channelsLock.Unlock()
|
||||
|
||||
ch, exists := notificationChannels[id]
|
||||
ch, exists := dn.channels[id]
|
||||
if exists {
|
||||
delete(notificationChannels, id)
|
||||
delete(dn.channels, id)
|
||||
}
|
||||
return ch, exists
|
||||
}
|
||||
|
||||
func cleanupChannel(id int) {
|
||||
notificationChannelsLock.Lock()
|
||||
defer notificationChannelsLock.Unlock()
|
||||
func (dn *darwinNotifier) cleanupChannel(id int) {
|
||||
dn.channelsLock.Lock()
|
||||
defer dn.channelsLock.Unlock()
|
||||
|
||||
if ch, exists := notificationChannels[id]; exists {
|
||||
delete(notificationChannels, id)
|
||||
if ch, exists := dn.channels[id]; exists {
|
||||
delete(dn.channels, id)
|
||||
close(ch)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,15 @@ import (
|
|||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
type linuxNotifier struct {
|
||||
categories map[string]NotificationCategory
|
||||
categoriesLock sync.RWMutex
|
||||
appName string
|
||||
internal *internalNotifier
|
||||
notificationInitErr error
|
||||
initOnce sync.Once
|
||||
}
|
||||
|
||||
const (
|
||||
dbusObjectPath = "/org/freedesktop/Notifications"
|
||||
dbusNotificationsInterface = "org.freedesktop.Notifications"
|
||||
|
|
@ -31,32 +40,8 @@ const (
|
|||
notifyChannelBufferSize = 25
|
||||
)
|
||||
|
||||
var (
|
||||
notificationLock sync.RWMutex
|
||||
notificationCategories = make(map[string]NotificationCategory)
|
||||
appName string
|
||||
initOnce sync.Once
|
||||
)
|
||||
|
||||
type closedReason uint32
|
||||
|
||||
func (r closedReason) string() string {
|
||||
switch r {
|
||||
case 1:
|
||||
return "expired"
|
||||
case 2:
|
||||
return "dismissed-by-user"
|
||||
case 3:
|
||||
return "closed-by-call"
|
||||
case 4:
|
||||
return "unknown"
|
||||
case 5:
|
||||
return "activated-by-user"
|
||||
default:
|
||||
return "other"
|
||||
}
|
||||
}
|
||||
|
||||
// internalNotifier handles the actual notification sending via dbus or command line
|
||||
type notificationContext struct {
|
||||
ID string
|
||||
|
|
@ -77,34 +62,36 @@ type internalNotifier struct {
|
|||
listenerRunning bool
|
||||
}
|
||||
|
||||
var notifier *internalNotifier
|
||||
|
||||
// New creates a new Notifications Service
|
||||
func New() *Service {
|
||||
notificationServiceOnce.Do(func() {
|
||||
if NotificationService == nil {
|
||||
NotificationService = &Service{}
|
||||
impl := &linuxNotifier{
|
||||
categories: make(map[string]NotificationCategory),
|
||||
}
|
||||
|
||||
NotificationService = &Service{
|
||||
impl: impl,
|
||||
}
|
||||
})
|
||||
return NotificationService
|
||||
}
|
||||
|
||||
// ServiceStartup is called when the service is loaded
|
||||
func (ns *Service) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
|
||||
appName = application.Get().Config().Name
|
||||
// Startup is called when the service is loaded
|
||||
func (ls *linuxNotifier) Startup(ctx context.Context) error {
|
||||
ls.appName = application.Get().Config().Name
|
||||
|
||||
if err := loadCategories(); err != nil {
|
||||
if err := ls.loadCategories(); err != nil {
|
||||
fmt.Printf("Failed to load notification categories: %v\n", err)
|
||||
}
|
||||
|
||||
notifier = &internalNotifier{
|
||||
ls.internal = &internalNotifier{
|
||||
activeNotifs: make(map[string]uint32),
|
||||
contexts: make(map[string]*notificationContext),
|
||||
}
|
||||
|
||||
var err error
|
||||
initOnce.Do(func() {
|
||||
err = notifier.init()
|
||||
ls.initOnce.Do(func() {
|
||||
err = ls.internal.init()
|
||||
})
|
||||
|
||||
return err
|
||||
|
|
@ -133,12 +120,12 @@ func (n *internalNotifier) shutdown() {
|
|||
n.sendPath = ""
|
||||
}
|
||||
|
||||
// ServiceShutdown is called when the service is unloaded
|
||||
func (ns *Service) ServiceShutdown() error {
|
||||
if notifier != nil {
|
||||
notifier.shutdown()
|
||||
// Shutdown is called when the service is unloaded
|
||||
func (ls *linuxNotifier) Shutdown() error {
|
||||
if ls.internal != nil {
|
||||
ls.internal.shutdown()
|
||||
}
|
||||
return saveCategories()
|
||||
return ls.saveCategories()
|
||||
}
|
||||
|
||||
// Initialize the notifier and choose the best available notification method
|
||||
|
|
@ -395,25 +382,25 @@ func (n *internalNotifier) handleActionInvoked(systemID uint32, actionKey string
|
|||
|
||||
// CheckBundleIdentifier is a Linux stub that always returns true.
|
||||
// (bundle identifiers are macOS-specific)
|
||||
func (ns *Service) CheckBundleIdentifier() bool {
|
||||
func (ls *linuxNotifier) CheckBundleIdentifier() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// RequestNotificationAuthorization is a Linux stub that always returns true.
|
||||
// (user authorization is macOS-specific)
|
||||
func (ns *Service) RequestNotificationAuthorization() (bool, error) {
|
||||
func (ls *linuxNotifier) RequestNotificationAuthorization() (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CheckNotificationAuthorization is a Linux stub that always returns true.
|
||||
// (user authorization is macOS-specific)
|
||||
func (ns *Service) CheckNotificationAuthorization() (bool, error) {
|
||||
func (ls *linuxNotifier) CheckNotificationAuthorization() (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// SendNotification sends a basic notification with a unique identifier, title, subtitle, and body.
|
||||
func (ns *Service) SendNotification(options NotificationOptions) error {
|
||||
if notifier == nil {
|
||||
func (ls *linuxNotifier) SendNotification(options NotificationOptions) error {
|
||||
if ls.internal == nil {
|
||||
return errors.New("notification service not initialized")
|
||||
}
|
||||
|
||||
|
|
@ -421,26 +408,26 @@ func (ns *Service) SendNotification(options NotificationOptions) error {
|
|||
return err
|
||||
}
|
||||
|
||||
notifier.Lock()
|
||||
defer notifier.Unlock()
|
||||
ls.internal.Lock()
|
||||
defer ls.internal.Unlock()
|
||||
|
||||
var (
|
||||
systemID uint32
|
||||
err error
|
||||
)
|
||||
|
||||
switch notifier.method {
|
||||
switch ls.internal.method {
|
||||
case MethodDbus:
|
||||
systemID, err = notifier.sendViaDbus(options, nil)
|
||||
systemID, err = ls.sendViaDbus(options, nil)
|
||||
case MethodNotifySend:
|
||||
systemID, err = notifier.sendViaNotifySend(options)
|
||||
systemID, err = ls.sendViaNotifySend(options)
|
||||
default:
|
||||
err = errors.New("no notification method is available")
|
||||
}
|
||||
|
||||
if err == nil && systemID > 0 {
|
||||
// Store the system ID mapping
|
||||
notifier.activeNotifs[options.ID] = systemID
|
||||
ls.internal.activeNotifs[options.ID] = systemID
|
||||
|
||||
// Create and store the notification context
|
||||
ctx := ¬ificationContext{
|
||||
|
|
@ -448,15 +435,15 @@ func (ns *Service) SendNotification(options NotificationOptions) error {
|
|||
SystemID: systemID,
|
||||
UserData: options.Data,
|
||||
}
|
||||
notifier.contexts[options.ID] = ctx
|
||||
ls.internal.contexts[options.ID] = ctx
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// SendNotificationWithActions sends a notification with additional actions.
|
||||
func (ns *Service) SendNotificationWithActions(options NotificationOptions) error {
|
||||
if notifier == nil {
|
||||
func (ls *linuxNotifier) SendNotificationWithActions(options NotificationOptions) error {
|
||||
if ls.internal == nil {
|
||||
return errors.New("notification service not initialized")
|
||||
}
|
||||
|
||||
|
|
@ -464,35 +451,35 @@ func (ns *Service) SendNotificationWithActions(options NotificationOptions) erro
|
|||
return err
|
||||
}
|
||||
|
||||
notificationLock.RLock()
|
||||
category, exists := notificationCategories[options.CategoryID]
|
||||
notificationLock.RUnlock()
|
||||
ls.categoriesLock.RLock()
|
||||
category, exists := ls.categories[options.CategoryID]
|
||||
ls.categoriesLock.RUnlock()
|
||||
|
||||
if !exists {
|
||||
return ns.SendNotification(options)
|
||||
return ls.SendNotification(options)
|
||||
}
|
||||
|
||||
notifier.Lock()
|
||||
defer notifier.Unlock()
|
||||
ls.internal.Lock()
|
||||
defer ls.internal.Unlock()
|
||||
|
||||
var (
|
||||
systemID uint32
|
||||
err error
|
||||
)
|
||||
|
||||
switch notifier.method {
|
||||
switch ls.internal.method {
|
||||
case MethodDbus:
|
||||
systemID, err = notifier.sendViaDbus(options, &category)
|
||||
systemID, err = ls.sendViaDbus(options, &category)
|
||||
case MethodNotifySend:
|
||||
// notify-send doesn't support actions, fall back to basic notification
|
||||
systemID, err = notifier.sendViaNotifySend(options)
|
||||
systemID, err = ls.sendViaNotifySend(options)
|
||||
default:
|
||||
err = errors.New("no notification method is available")
|
||||
}
|
||||
|
||||
if err == nil && systemID > 0 {
|
||||
// Store the system ID mapping
|
||||
notifier.activeNotifs[options.ID] = systemID
|
||||
ls.internal.activeNotifs[options.ID] = systemID
|
||||
|
||||
// Create and store the notification context with actions
|
||||
ctx := ¬ificationContext{
|
||||
|
|
@ -509,14 +496,14 @@ func (ns *Service) SendNotificationWithActions(options NotificationOptions) erro
|
|||
}
|
||||
}
|
||||
|
||||
notifier.contexts[options.ID] = ctx
|
||||
ls.internal.contexts[options.ID] = ctx
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// sendViaDbus sends a notification via dbus
|
||||
func (n *internalNotifier) sendViaDbus(options NotificationOptions, category *NotificationCategory) (result uint32, err error) {
|
||||
func (ls *linuxNotifier) sendViaDbus(options NotificationOptions, category *NotificationCategory) (result uint32, err error) {
|
||||
// Prepare actions
|
||||
var actions []string
|
||||
if category != nil {
|
||||
|
|
@ -542,9 +529,9 @@ func (n *internalNotifier) sendViaDbus(options NotificationOptions, category *No
|
|||
}
|
||||
|
||||
// Send the notification
|
||||
obj := n.dbusConn.Object(dbusNotificationsInterface, dbusObjectPath)
|
||||
obj := ls.internal.dbusConn.Object(dbusNotificationsInterface, dbusObjectPath)
|
||||
dbusArgs := []interface{}{
|
||||
appName, // App name
|
||||
ls.appName, // App name
|
||||
uint32(0), // Replaces ID (0 means new notification)
|
||||
"", // App icon (empty for now)
|
||||
options.Title, // Title
|
||||
|
|
@ -568,7 +555,7 @@ func (n *internalNotifier) sendViaDbus(options NotificationOptions, category *No
|
|||
}
|
||||
|
||||
// sendViaNotifySend sends a notification via notify-send command
|
||||
func (n *internalNotifier) sendViaNotifySend(options NotificationOptions) (uint32, error) {
|
||||
func (ls *linuxNotifier) sendViaNotifySend(options NotificationOptions) (uint32, error) {
|
||||
args := []string{
|
||||
options.Title,
|
||||
options.Body,
|
||||
|
|
@ -581,7 +568,7 @@ func (n *internalNotifier) sendViaNotifySend(options NotificationOptions) (uint3
|
|||
args = append(args, "--urgency=normal")
|
||||
|
||||
// Execute the command
|
||||
cmd := exec.Command(n.sendPath, args...)
|
||||
cmd := exec.Command(ls.internal.sendPath, args...)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("notify-send error: %v", err)
|
||||
|
|
@ -592,77 +579,77 @@ func (n *internalNotifier) sendViaNotifySend(options NotificationOptions) (uint3
|
|||
}
|
||||
|
||||
// RegisterNotificationCategory registers a new NotificationCategory to be used with SendNotificationWithActions.
|
||||
func (ns *Service) RegisterNotificationCategory(category NotificationCategory) error {
|
||||
notificationLock.Lock()
|
||||
notificationCategories[category.ID] = category
|
||||
notificationLock.Unlock()
|
||||
func (ls *linuxNotifier) RegisterNotificationCategory(category NotificationCategory) error {
|
||||
ls.categoriesLock.Lock()
|
||||
ls.categories[category.ID] = category
|
||||
ls.categoriesLock.Unlock()
|
||||
|
||||
return saveCategories()
|
||||
return ls.saveCategories()
|
||||
}
|
||||
|
||||
// RemoveNotificationCategory removes a previously registered NotificationCategory.
|
||||
func (ns *Service) RemoveNotificationCategory(categoryId string) error {
|
||||
notificationLock.Lock()
|
||||
delete(notificationCategories, categoryId)
|
||||
notificationLock.Unlock()
|
||||
func (ls *linuxNotifier) RemoveNotificationCategory(categoryId string) error {
|
||||
ls.categoriesLock.Lock()
|
||||
delete(ls.categories, categoryId)
|
||||
ls.categoriesLock.Unlock()
|
||||
|
||||
return saveCategories()
|
||||
return ls.saveCategories()
|
||||
}
|
||||
|
||||
// RemoveAllPendingNotifications is a Linux stub that always returns nil.
|
||||
// (macOS-specific)
|
||||
func (ns *Service) RemoveAllPendingNotifications() error {
|
||||
func (ls *linuxNotifier) RemoveAllPendingNotifications() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemovePendingNotification is a Linux stub that always returns nil.
|
||||
// (macOS-specific)
|
||||
func (ns *Service) RemovePendingNotification(_ string) error {
|
||||
func (ls *linuxNotifier) RemovePendingNotification(_ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveAllDeliveredNotifications is a Linux stub that always returns nil.
|
||||
// (macOS-specific)
|
||||
func (ns *Service) RemoveAllDeliveredNotifications() error {
|
||||
func (ls *linuxNotifier) RemoveAllDeliveredNotifications() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveDeliveredNotification is a Linux stub that always returns nil.
|
||||
// (macOS-specific)
|
||||
func (ns *Service) RemoveDeliveredNotification(_ string) error {
|
||||
func (ls *linuxNotifier) RemoveDeliveredNotification(_ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveNotification removes a notification by ID (Linux-specific)
|
||||
func (ns *Service) RemoveNotification(identifier string) error {
|
||||
if notifier == nil || notifier.method != MethodDbus || notifier.dbusConn == nil {
|
||||
func (ls *linuxNotifier) RemoveNotification(identifier string) error {
|
||||
if ls.internal == nil || ls.internal.method != MethodDbus || ls.internal.dbusConn == nil {
|
||||
return errors.New("dbus not available for closing notifications")
|
||||
}
|
||||
|
||||
// Get the system ID for this notification
|
||||
notifier.Lock()
|
||||
systemID, exists := notifier.activeNotifs[identifier]
|
||||
notifier.Unlock()
|
||||
ls.internal.Lock()
|
||||
systemID, exists := ls.internal.activeNotifs[identifier]
|
||||
ls.internal.Unlock()
|
||||
|
||||
if !exists {
|
||||
return nil // Already closed or unknown
|
||||
}
|
||||
|
||||
// Call CloseNotification on dbus
|
||||
obj := notifier.dbusConn.Object(dbusNotificationsInterface, dbusObjectPath)
|
||||
obj := ls.internal.dbusConn.Object(dbusNotificationsInterface, dbusObjectPath)
|
||||
call := obj.Call(callCloseNotification, 0, systemID)
|
||||
|
||||
return call.Err
|
||||
}
|
||||
|
||||
// getConfigFilePath returns the path to the configuration file for storing notification categories
|
||||
func getConfigFilePath() (string, error) {
|
||||
func (ls *linuxNotifier) getConfigFilePath() (string, error) {
|
||||
configDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get user config directory: %v", err)
|
||||
}
|
||||
|
||||
appConfigDir := filepath.Join(configDir, appName)
|
||||
appConfigDir := filepath.Join(configDir, ls.appName)
|
||||
if err := os.MkdirAll(appConfigDir, 0755); err != nil {
|
||||
return "", fmt.Errorf("failed to create config directory: %v", err)
|
||||
}
|
||||
|
|
@ -671,15 +658,15 @@ func getConfigFilePath() (string, error) {
|
|||
}
|
||||
|
||||
// saveCategories saves the notification categories to a file.
|
||||
func saveCategories() error {
|
||||
filePath, err := getConfigFilePath()
|
||||
func (ls *linuxNotifier) saveCategories() error {
|
||||
filePath, err := ls.getConfigFilePath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
notificationLock.RLock()
|
||||
data, err := json.Marshal(notificationCategories)
|
||||
notificationLock.RUnlock()
|
||||
ls.categoriesLock.RLock()
|
||||
data, err := json.Marshal(ls.categories)
|
||||
ls.categoriesLock.RUnlock()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal notification categories: %v", err)
|
||||
|
|
@ -693,8 +680,8 @@ func saveCategories() error {
|
|||
}
|
||||
|
||||
// loadCategories loads notification categories from a file.
|
||||
func loadCategories() error {
|
||||
filePath, err := getConfigFilePath()
|
||||
func (ls *linuxNotifier) loadCategories() error {
|
||||
filePath, err := ls.getConfigFilePath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -717,9 +704,26 @@ func loadCategories() error {
|
|||
return fmt.Errorf("failed to unmarshal notification categories: %v", err)
|
||||
}
|
||||
|
||||
notificationLock.Lock()
|
||||
notificationCategories = categories
|
||||
notificationLock.Unlock()
|
||||
ls.categoriesLock.Lock()
|
||||
ls.categories = categories
|
||||
ls.categoriesLock.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r closedReason) string() string {
|
||||
switch r {
|
||||
case 1:
|
||||
return "expired"
|
||||
case 2:
|
||||
return "dismissed-by-user"
|
||||
case 3:
|
||||
return "closed-by-call"
|
||||
case 4:
|
||||
return "unknown"
|
||||
case 5:
|
||||
return "activated-by-user"
|
||||
default:
|
||||
return "other"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,13 +19,13 @@ import (
|
|||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
var (
|
||||
NotificationCategories = make(map[string]NotificationCategory)
|
||||
notificationCategoriesLock sync.RWMutex
|
||||
appName string
|
||||
appGUID string
|
||||
iconPath string
|
||||
)
|
||||
type windowsNotifier struct {
|
||||
categories map[string]NotificationCategory
|
||||
categoriesLock sync.RWMutex
|
||||
appName string
|
||||
appGUID string
|
||||
iconPath string
|
||||
}
|
||||
|
||||
const (
|
||||
ToastRegistryPath = `Software\Classes\AppUserModelId\`
|
||||
|
|
@ -43,31 +43,35 @@ type NotificationPayload struct {
|
|||
// Creates a new Notifications Service.
|
||||
func New() *Service {
|
||||
notificationServiceOnce.Do(func() {
|
||||
if NotificationService == nil {
|
||||
NotificationService = &Service{}
|
||||
impl := &windowsNotifier{
|
||||
categories: make(map[string]NotificationCategory),
|
||||
}
|
||||
|
||||
NotificationService = &Service{
|
||||
impl: impl,
|
||||
}
|
||||
})
|
||||
|
||||
return NotificationService
|
||||
}
|
||||
|
||||
// ServiceStartup is called when the service is loaded
|
||||
// Startup is called when the service is loaded
|
||||
// Sets an activation callback to emit an event when notifications are interacted with.
|
||||
func (ns *Service) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
|
||||
appName = application.Get().Config().Name
|
||||
func (wn *windowsNotifier) Startup(ctx context.Context) error {
|
||||
wn.appName = application.Get().Config().Name
|
||||
|
||||
guid, err := getGUID()
|
||||
guid, err := wn.getGUID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appGUID = guid
|
||||
wn.appGUID = guid
|
||||
|
||||
iconPath = filepath.Join(os.TempDir(), appName+appGUID+".png")
|
||||
wn.iconPath = filepath.Join(os.TempDir(), wn.appName+wn.appGUID+".png")
|
||||
|
||||
toast.SetAppData(toast.AppData{
|
||||
AppID: appName,
|
||||
AppID: wn.appName,
|
||||
GUID: guid,
|
||||
IconPath: iconPath,
|
||||
IconPath: wn.iconPath,
|
||||
})
|
||||
|
||||
toast.SetActivationCallback(func(args string, data []toast.UserData) {
|
||||
|
|
@ -89,7 +93,7 @@ func (ns *Service) ServiceStartup(ctx context.Context, options application.Servi
|
|||
response.UserInfo = userInfoMap
|
||||
}
|
||||
|
||||
if userText, found := getUserText(data); found {
|
||||
if userText, found := wn.getUserText(data); found {
|
||||
response.UserText = userText
|
||||
}
|
||||
|
||||
|
|
@ -99,40 +103,34 @@ func (ns *Service) ServiceStartup(ctx context.Context, options application.Servi
|
|||
}
|
||||
})
|
||||
|
||||
return loadCategoriesFromRegistry()
|
||||
return wn.loadCategoriesFromRegistry()
|
||||
}
|
||||
|
||||
// ServiceShutdown is called when the service is unloaded
|
||||
func (ns *Service) ServiceShutdown() error {
|
||||
return saveCategoriesToRegistry()
|
||||
}
|
||||
|
||||
// CheckBundleIdentifier is a Windows stub that always returns true.
|
||||
// (bundle identifiers are macOS-specific)
|
||||
func CheckBundleIdentifier() bool {
|
||||
return true
|
||||
// Shutdown will attempt to save the categories to the registry when the service unloads
|
||||
func (wn *windowsNotifier) Shutdown() error {
|
||||
return wn.saveCategoriesToRegistry()
|
||||
}
|
||||
|
||||
// RequestNotificationAuthorization is a Windows stub that always returns true, nil.
|
||||
// (user authorization is macOS-specific)
|
||||
func (ns *Service) RequestNotificationAuthorization() (bool, error) {
|
||||
func (wn *windowsNotifier) RequestNotificationAuthorization() (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CheckNotificationAuthorization is a Windows stub that always returns true.
|
||||
// (user authorization is macOS-specific)
|
||||
func (ns *Service) CheckNotificationAuthorization() bool {
|
||||
return true
|
||||
func (wn *windowsNotifier) CheckNotificationAuthorization() (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// SendNotification sends a basic notification with a name, title, and body. All other options are ignored on Windows.
|
||||
// (subtitle and category id are only available on macOS)
|
||||
func (ns *Service) SendNotification(options NotificationOptions) error {
|
||||
// (subtitle is only available on macOS)
|
||||
func (wn *windowsNotifier) SendNotification(options NotificationOptions) error {
|
||||
if err := validateNotificationOptions(options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := saveIconToDir(); err != nil {
|
||||
if err := wn.saveIconToDir(); err != nil {
|
||||
fmt.Printf("Error saving icon: %v\n", err)
|
||||
}
|
||||
|
||||
|
|
@ -143,7 +141,7 @@ func (ns *Service) SendNotification(options NotificationOptions) error {
|
|||
}
|
||||
|
||||
if options.Data != nil {
|
||||
encodedPayload, err := encodePayload(DefaultActionIdentifier, options.Data)
|
||||
encodedPayload, err := wn.encodePayload(DefaultActionIdentifier, options.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode notification data: %w", err)
|
||||
}
|
||||
|
|
@ -156,19 +154,19 @@ func (ns *Service) SendNotification(options NotificationOptions) error {
|
|||
// SendNotificationWithActions sends a notification with additional actions and inputs.
|
||||
// A NotificationCategory must be registered with RegisterNotificationCategory first. The `CategoryID` must match the registered category.
|
||||
// If a NotificationCategory is not registered a basic notification will be sent.
|
||||
// (subtitle and category id are only available on macOS)
|
||||
func (ns *Service) SendNotificationWithActions(options NotificationOptions) error {
|
||||
// (subtitle is only available on macOS)
|
||||
func (wn *windowsNotifier) SendNotificationWithActions(options NotificationOptions) error {
|
||||
if err := validateNotificationOptions(options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := saveIconToDir(); err != nil {
|
||||
if err := wn.saveIconToDir(); err != nil {
|
||||
fmt.Printf("Error saving icon: %v\n", err)
|
||||
}
|
||||
|
||||
notificationCategoriesLock.RLock()
|
||||
nCategory := NotificationCategories[options.CategoryID]
|
||||
notificationCategoriesLock.RUnlock()
|
||||
wn.categoriesLock.RLock()
|
||||
nCategory := wn.categories[options.CategoryID]
|
||||
wn.categoriesLock.RUnlock()
|
||||
|
||||
n := toast.Notification{
|
||||
Title: options.Title,
|
||||
|
|
@ -197,14 +195,14 @@ func (ns *Service) SendNotificationWithActions(options NotificationOptions) erro
|
|||
}
|
||||
|
||||
if options.Data != nil {
|
||||
encodedPayload, err := encodePayload(n.ActivationArguments, options.Data)
|
||||
encodedPayload, err := wn.encodePayload(n.ActivationArguments, options.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode notification data: %w", err)
|
||||
}
|
||||
n.ActivationArguments = encodedPayload
|
||||
|
||||
for index := range n.Actions {
|
||||
encodedPayload, err := encodePayload(n.Actions[index].Arguments, options.Data)
|
||||
encodedPayload, err := wn.encodePayload(n.Actions[index].Arguments, options.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode notification data: %w", err)
|
||||
}
|
||||
|
|
@ -217,61 +215,63 @@ func (ns *Service) SendNotificationWithActions(options NotificationOptions) erro
|
|||
|
||||
// RegisterNotificationCategory registers a new NotificationCategory to be used with SendNotificationWithActions.
|
||||
// Registering a category with the same name as a previously registered NotificationCategory will override it.
|
||||
func (ns *Service) RegisterNotificationCategory(category NotificationCategory) error {
|
||||
notificationCategoriesLock.Lock()
|
||||
NotificationCategories[category.ID] = NotificationCategory{
|
||||
func (wn *windowsNotifier) RegisterNotificationCategory(category NotificationCategory) error {
|
||||
wn.categoriesLock.Lock()
|
||||
defer wn.categoriesLock.Unlock()
|
||||
|
||||
wn.categories[category.ID] = NotificationCategory{
|
||||
ID: category.ID,
|
||||
Actions: category.Actions,
|
||||
HasReplyField: bool(category.HasReplyField),
|
||||
ReplyPlaceholder: category.ReplyPlaceholder,
|
||||
ReplyButtonTitle: category.ReplyButtonTitle,
|
||||
}
|
||||
notificationCategoriesLock.Unlock()
|
||||
|
||||
return saveCategoriesToRegistry()
|
||||
return wn.saveCategoriesToRegistry()
|
||||
}
|
||||
|
||||
// RemoveNotificationCategory removes a previously registered NotificationCategory.
|
||||
func (ns *Service) RemoveNotificationCategory(categoryId string) error {
|
||||
notificationCategoriesLock.Lock()
|
||||
delete(NotificationCategories, categoryId)
|
||||
notificationCategoriesLock.Unlock()
|
||||
func (wn *windowsNotifier) RemoveNotificationCategory(categoryId string) error {
|
||||
wn.categoriesLock.Lock()
|
||||
defer wn.categoriesLock.Unlock()
|
||||
|
||||
return saveCategoriesToRegistry()
|
||||
delete(wn.categories, categoryId)
|
||||
|
||||
return wn.saveCategoriesToRegistry()
|
||||
}
|
||||
|
||||
// RemoveAllPendingNotifications is a Windows stub that always returns nil.
|
||||
// (macOS-specific)
|
||||
func (ns *Service) RemoveAllPendingNotifications() error {
|
||||
func (wn *windowsNotifier) RemoveAllPendingNotifications() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemovePendingNotification is a Windows stub that always returns nil.
|
||||
// (macOS-specific)
|
||||
func (ns *Service) RemovePendingNotification(_ string) error {
|
||||
func (wn *windowsNotifier) RemovePendingNotification(_ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveAllDeliveredNotifications is a Windows stub that always returns nil.
|
||||
// (macOS-specific)
|
||||
func (ns *Service) RemoveAllDeliveredNotifications() error {
|
||||
func (wn *windowsNotifier) RemoveAllDeliveredNotifications() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveDeliveredNotification is a Windows stub that always returns nil.
|
||||
// (macOS-specific)
|
||||
func (ns *Service) RemoveDeliveredNotification(_ string) error {
|
||||
func (wn *windowsNotifier) RemoveDeliveredNotification(_ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveNotification is a Windows stub that always returns nil.
|
||||
// (Linux-specific)
|
||||
func (ns *Service) RemoveNotification(identifier string) error {
|
||||
func (wn *windowsNotifier) RemoveNotification(identifier string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// encodePayload combines an action ID and user data into a single encoded string
|
||||
func encodePayload(actionID string, data map[string]interface{}) (string, error) {
|
||||
func (wn *windowsNotifier) encodePayload(actionID string, data map[string]interface{}) (string, error) {
|
||||
payload := NotificationPayload{
|
||||
Action: actionID,
|
||||
Data: data,
|
||||
|
|
@ -315,20 +315,20 @@ func parseNotificationResponse(response string) (action string, data string) {
|
|||
return actionID, ""
|
||||
}
|
||||
|
||||
func saveIconToDir() error {
|
||||
func (wn *windowsNotifier) saveIconToDir() error {
|
||||
icon, err := application.NewIconFromResource(w32.GetModuleHandle(""), uint16(3))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to retrieve application icon: %w", err)
|
||||
}
|
||||
|
||||
return saveHIconAsPNG(icon, iconPath)
|
||||
return saveHIconAsPNG(icon, wn.iconPath)
|
||||
}
|
||||
|
||||
func saveCategoriesToRegistry() error {
|
||||
notificationCategoriesLock.Lock()
|
||||
defer notificationCategoriesLock.Unlock()
|
||||
func (wn *windowsNotifier) saveCategoriesToRegistry() error {
|
||||
wn.categoriesLock.Lock()
|
||||
defer wn.categoriesLock.Unlock()
|
||||
|
||||
registryPath := fmt.Sprintf(NotificationCategoriesRegistryPath, appName)
|
||||
registryPath := fmt.Sprintf(NotificationCategoriesRegistryPath, wn.appName)
|
||||
|
||||
key, _, err := registry.CreateKey(
|
||||
registry.CURRENT_USER,
|
||||
|
|
@ -340,7 +340,7 @@ func saveCategoriesToRegistry() error {
|
|||
}
|
||||
defer key.Close()
|
||||
|
||||
data, err := json.Marshal(NotificationCategories)
|
||||
data, err := json.Marshal(wn.categories)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -348,11 +348,11 @@ func saveCategoriesToRegistry() error {
|
|||
return key.SetStringValue(NotificationCategoriesRegistryKey, string(data))
|
||||
}
|
||||
|
||||
func loadCategoriesFromRegistry() error {
|
||||
notificationCategoriesLock.Lock()
|
||||
defer notificationCategoriesLock.Unlock()
|
||||
func (wn *windowsNotifier) loadCategoriesFromRegistry() error {
|
||||
wn.categoriesLock.Lock()
|
||||
defer wn.categoriesLock.Unlock()
|
||||
|
||||
registryPath := fmt.Sprintf(NotificationCategoriesRegistryPath, appName)
|
||||
registryPath := fmt.Sprintf(NotificationCategoriesRegistryPath, wn.appName)
|
||||
|
||||
key, err := registry.OpenKey(
|
||||
registry.CURRENT_USER,
|
||||
|
|
@ -382,12 +382,12 @@ func loadCategoriesFromRegistry() error {
|
|||
return fmt.Errorf("failed to parse notification categories from registry: %w", err)
|
||||
}
|
||||
|
||||
NotificationCategories = categories
|
||||
wn.categories = categories
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getUserText(data []toast.UserData) (string, bool) {
|
||||
func (wn *windowsNotifier) getUserText(data []toast.UserData) (string, bool) {
|
||||
for _, d := range data {
|
||||
if d.Key == "userText" {
|
||||
return d.Value, true
|
||||
|
|
@ -396,8 +396,8 @@ func getUserText(data []toast.UserData) (string, bool) {
|
|||
return "", false
|
||||
}
|
||||
|
||||
func getGUID() (string, error) {
|
||||
keyPath := ToastRegistryPath + appName
|
||||
func (wn *windowsNotifier) getGUID() (string, error) {
|
||||
keyPath := ToastRegistryPath + wn.appName
|
||||
|
||||
k, err := registry.OpenKey(registry.CURRENT_USER, keyPath, registry.QUERY_VALUE)
|
||||
if err == nil {
|
||||
|
|
@ -408,7 +408,7 @@ func getGUID() (string, error) {
|
|||
}
|
||||
}
|
||||
|
||||
guid := generateGUID()
|
||||
guid := wn.generateGUID()
|
||||
|
||||
k, _, err = registry.CreateKey(registry.CURRENT_USER, keyPath, registry.WRITE)
|
||||
if err != nil {
|
||||
|
|
@ -423,7 +423,7 @@ func getGUID() (string, error) {
|
|||
return guid, nil
|
||||
}
|
||||
|
||||
func generateGUID() string {
|
||||
func (wn *windowsNotifier) generateGUID() string {
|
||||
guid := uuid.New()
|
||||
return fmt.Sprintf("{%s}", guid.String())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue