mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-09 11:31:38 +08:00
refactor/simplify linux
This commit is contained in:
parent
9adeef2bdf
commit
5dc7bd97f2
@ -17,12 +17,36 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type linuxNotifier struct {
|
type linuxNotifier struct {
|
||||||
|
// Categories
|
||||||
categories map[string]NotificationCategory
|
categories map[string]NotificationCategory
|
||||||
categoriesLock sync.RWMutex
|
categoriesLock sync.RWMutex
|
||||||
|
|
||||||
|
// App info
|
||||||
appName string
|
appName string
|
||||||
internal *internalNotifier
|
|
||||||
notificationInitErr error
|
// Notification system
|
||||||
|
sync.Mutex
|
||||||
|
method string
|
||||||
|
dbusConn *dbus.Conn
|
||||||
|
sendPath string
|
||||||
|
activeNotifs map[string]uint32 // Maps our notification IDs to system IDs
|
||||||
|
contexts map[string]*notificationContext // Stores notification contexts by our ID
|
||||||
|
|
||||||
|
// Listener management
|
||||||
|
listenerCtx context.Context
|
||||||
|
listenerCancel context.CancelFunc
|
||||||
|
listenerRunning bool
|
||||||
|
|
||||||
|
// Initialization
|
||||||
initOnce sync.Once
|
initOnce sync.Once
|
||||||
|
initialized bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type notificationContext struct {
|
||||||
|
ID string
|
||||||
|
SystemID uint32
|
||||||
|
Actions map[string]string // Maps action keys to display labels
|
||||||
|
UserData map[string]interface{} // The original user data
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -35,38 +59,19 @@ const (
|
|||||||
|
|
||||||
MethodNotifySend = "notify-send"
|
MethodNotifySend = "notify-send"
|
||||||
MethodDbus = "dbus"
|
MethodDbus = "dbus"
|
||||||
MethodKdialog = "kdialog"
|
|
||||||
|
|
||||||
notifyChannelBufferSize = 25
|
notifyChannelBufferSize = 25
|
||||||
)
|
)
|
||||||
|
|
||||||
type closedReason uint32
|
type closedReason uint32
|
||||||
|
|
||||||
// internalNotifier handles the actual notification sending via dbus or command line
|
|
||||||
type notificationContext struct {
|
|
||||||
ID string
|
|
||||||
SystemID uint32
|
|
||||||
Actions map[string]string // Maps action keys to display labels
|
|
||||||
UserData map[string]interface{} // The original user data
|
|
||||||
}
|
|
||||||
|
|
||||||
type internalNotifier struct {
|
|
||||||
sync.Mutex
|
|
||||||
method string
|
|
||||||
dbusConn *dbus.Conn
|
|
||||||
sendPath string
|
|
||||||
activeNotifs map[string]uint32 // Maps our notification IDs to system IDs
|
|
||||||
contexts map[string]*notificationContext // Stores notification contexts by our ID
|
|
||||||
listenerCtx context.Context
|
|
||||||
listenerCancel context.CancelFunc
|
|
||||||
listenerRunning bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new Notifications Service
|
// New creates a new Notifications Service
|
||||||
func New() *Service {
|
func New() *Service {
|
||||||
notificationServiceOnce.Do(func() {
|
notificationServiceOnce.Do(func() {
|
||||||
impl := &linuxNotifier{
|
impl := &linuxNotifier{
|
||||||
categories: make(map[string]NotificationCategory),
|
categories: make(map[string]NotificationCategory),
|
||||||
|
activeNotifs: make(map[string]uint32),
|
||||||
|
contexts: make(map[string]*notificationContext),
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationService = &Service{
|
NotificationService = &Service{
|
||||||
@ -77,97 +82,96 @@ func New() *Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Startup is called when the service is loaded
|
// Startup is called when the service is loaded
|
||||||
func (ls *linuxNotifier) Startup(ctx context.Context) error {
|
func (ln *linuxNotifier) Startup(ctx context.Context) error {
|
||||||
ls.appName = application.Get().Config().Name
|
ln.appName = application.Get().Config().Name
|
||||||
|
|
||||||
if err := ls.loadCategories(); err != nil {
|
if err := ln.loadCategories(); err != nil {
|
||||||
fmt.Printf("Failed to load notification categories: %v\n", err)
|
fmt.Printf("Failed to load notification categories: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the internal notifier
|
|
||||||
ls.internal = &internalNotifier{
|
|
||||||
activeNotifs: make(map[string]uint32),
|
|
||||||
contexts: make(map[string]*notificationContext),
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
ls.initOnce.Do(func() {
|
ln.initOnce.Do(func() {
|
||||||
// Initialize notification system
|
err = ln.initNotificationSystem()
|
||||||
err = ls.initNotificationSystem()
|
ln.initialized = err == nil
|
||||||
})
|
})
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown is called when the service is unloaded
|
// initNotificationSystem initializes the notification system
|
||||||
func (ls *linuxNotifier) Shutdown() error {
|
func (ln *linuxNotifier) initNotificationSystem() error {
|
||||||
if ls.internal != nil {
|
ln.Lock()
|
||||||
ls.internal.Lock()
|
defer ln.Unlock()
|
||||||
defer ls.internal.Unlock()
|
|
||||||
|
|
||||||
// Cancel the listener context if it's running
|
// Cancel any existing listener
|
||||||
if ls.internal.listenerCancel != nil {
|
if ln.listenerCancel != nil {
|
||||||
ls.internal.listenerCancel()
|
ln.listenerCancel()
|
||||||
ls.internal.listenerCancel = nil
|
ln.listenerCancel = nil
|
||||||
}
|
|
||||||
|
|
||||||
// Close the connection
|
|
||||||
if ls.internal.dbusConn != nil {
|
|
||||||
ls.internal.dbusConn.Close()
|
|
||||||
ls.internal.dbusConn = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear state
|
|
||||||
ls.internal.activeNotifs = make(map[string]uint32)
|
|
||||||
ls.internal.contexts = make(map[string]*notificationContext)
|
|
||||||
ls.internal.method = "none"
|
|
||||||
ls.internal.sendPath = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return ls.saveCategories()
|
|
||||||
}
|
|
||||||
|
|
||||||
// initNotificationSystem initializes the notification system, choosing the best available method
|
|
||||||
func (ls *linuxNotifier) initNotificationSystem() error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// Cancel any existing listener before starting a new one
|
|
||||||
if ls.internal.listenerCancel != nil {
|
|
||||||
ls.internal.listenerCancel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new context for the listener
|
// Create a new context for the listener
|
||||||
ls.internal.listenerCtx, ls.internal.listenerCancel = context.WithCancel(context.Background())
|
ln.listenerCtx, ln.listenerCancel = context.WithCancel(context.Background())
|
||||||
|
|
||||||
// Reset state
|
// Reset state
|
||||||
ls.internal.activeNotifs = make(map[string]uint32)
|
ln.activeNotifs = make(map[string]uint32)
|
||||||
ls.internal.contexts = make(map[string]*notificationContext)
|
ln.contexts = make(map[string]*notificationContext)
|
||||||
ls.internal.listenerRunning = false
|
ln.listenerRunning = false
|
||||||
|
|
||||||
checkDbus := func() (*dbus.Conn, error) {
|
// Try dbus first
|
||||||
|
dbusConn, err := ln.initDBus()
|
||||||
|
if err == nil {
|
||||||
|
ln.dbusConn = dbusConn
|
||||||
|
ln.method = MethodDbus
|
||||||
|
|
||||||
|
// Start the dbus signal listener
|
||||||
|
go ln.startDBusListener(ln.listenerCtx)
|
||||||
|
ln.listenerRunning = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try notify-send as fallback
|
||||||
|
sendPath, err := ln.initNotifySend()
|
||||||
|
if err == nil {
|
||||||
|
ln.sendPath = sendPath
|
||||||
|
ln.method = MethodNotifySend
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// No method available
|
||||||
|
ln.method = ""
|
||||||
|
ln.sendPath = ""
|
||||||
|
return errors.New("no notification method is available")
|
||||||
|
}
|
||||||
|
|
||||||
|
// initDBus attempts to initialize D-Bus notifications
|
||||||
|
func (ln *linuxNotifier) initDBus() (*dbus.Conn, error) {
|
||||||
conn, err := dbus.SessionBusPrivate()
|
conn, err := dbus.SessionBusPrivate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return conn, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = conn.Auth(nil); err != nil {
|
if err = conn.Auth(nil); err != nil {
|
||||||
return conn, err
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = conn.Hello(); err != nil {
|
if err = conn.Hello(); err != nil {
|
||||||
return conn, err
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
obj := conn.Object(dbusNotificationsInterface, dbusObjectPath)
|
obj := conn.Object(dbusNotificationsInterface, dbusObjectPath)
|
||||||
call := obj.Call(callGetCapabilities, 0)
|
call := obj.Call(callGetCapabilities, 0)
|
||||||
if call.Err != nil {
|
if call.Err != nil {
|
||||||
return conn, call.Err
|
conn.Close()
|
||||||
|
return nil, call.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
var ret []string
|
var ret []string
|
||||||
err = call.Store(&ret)
|
err = call.Store(&ret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return conn, err
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a listener for notification signals
|
// Add a listener for notification signals
|
||||||
@ -176,59 +180,68 @@ func (ls *linuxNotifier) initNotificationSystem() error {
|
|||||||
dbus.WithMatchInterface(dbusNotificationsInterface),
|
dbus.WithMatchInterface(dbusNotificationsInterface),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try dbus first
|
// initNotifySend attempts to find notify-send binary
|
||||||
ls.internal.dbusConn, err = checkDbus()
|
func (ln *linuxNotifier) initNotifySend() (string, error) {
|
||||||
if err == nil {
|
// Try standard notify-send
|
||||||
ls.internal.method = MethodDbus
|
|
||||||
// Start the dbus signal listener with context
|
|
||||||
go ls.startDBusListener(ls.internal.listenerCtx)
|
|
||||||
ls.internal.listenerRunning = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if ls.internal.dbusConn != nil {
|
|
||||||
ls.internal.dbusConn.Close()
|
|
||||||
ls.internal.dbusConn = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try notify-send
|
|
||||||
send, err := exec.LookPath("notify-send")
|
send, err := exec.LookPath("notify-send")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
ls.internal.sendPath = send
|
return send, nil
|
||||||
ls.internal.method = MethodNotifySend
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try sw-notify-send
|
// Try sw-notify-send (in some distros)
|
||||||
send, err = exec.LookPath("sw-notify-send")
|
send, err = exec.LookPath("sw-notify-send")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
ls.internal.sendPath = send
|
return send, nil
|
||||||
ls.internal.method = MethodNotifySend
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// No method available
|
return "", errors.New("notify-send not found")
|
||||||
ls.internal.method = "none"
|
}
|
||||||
ls.internal.sendPath = ""
|
|
||||||
|
|
||||||
return errors.New("no notification method is available")
|
// Shutdown is called when the service is unloaded
|
||||||
|
func (ln *linuxNotifier) Shutdown() error {
|
||||||
|
ln.Lock()
|
||||||
|
|
||||||
|
// Cancel the listener context if it's running
|
||||||
|
if ln.listenerCancel != nil {
|
||||||
|
ln.listenerCancel()
|
||||||
|
ln.listenerCancel = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the connection
|
||||||
|
if ln.dbusConn != nil {
|
||||||
|
ln.dbusConn.Close()
|
||||||
|
ln.dbusConn = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear state
|
||||||
|
ln.activeNotifs = make(map[string]uint32)
|
||||||
|
ln.contexts = make(map[string]*notificationContext)
|
||||||
|
ln.method = ""
|
||||||
|
ln.sendPath = ""
|
||||||
|
ln.initialized = false
|
||||||
|
|
||||||
|
ln.Unlock()
|
||||||
|
|
||||||
|
return ln.saveCategories()
|
||||||
}
|
}
|
||||||
|
|
||||||
// startDBusListener listens for DBus signals for notification actions and closures
|
// startDBusListener listens for DBus signals for notification actions and closures
|
||||||
func (ls *linuxNotifier) startDBusListener(ctx context.Context) {
|
func (ln *linuxNotifier) startDBusListener(ctx context.Context) {
|
||||||
signal := make(chan *dbus.Signal, notifyChannelBufferSize)
|
signal := make(chan *dbus.Signal, notifyChannelBufferSize)
|
||||||
ls.internal.dbusConn.Signal(signal)
|
ln.dbusConn.Signal(signal)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
ls.internal.Lock()
|
ln.Lock()
|
||||||
ls.internal.listenerRunning = false
|
ln.listenerRunning = false
|
||||||
ls.internal.Unlock()
|
ln.Unlock()
|
||||||
ls.internal.dbusConn.RemoveSignal(signal) // Remove signal handler
|
ln.dbusConn.RemoveSignal(signal) // Remove signal handler
|
||||||
close(signal) // Clean up channel
|
close(signal) // Clean up channel
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -252,34 +265,34 @@ func (ls *linuxNotifier) startDBusListener(ctx context.Context) {
|
|||||||
case signalNotificationClosed:
|
case signalNotificationClosed:
|
||||||
systemID := s.Body[0].(uint32)
|
systemID := s.Body[0].(uint32)
|
||||||
reason := closedReason(s.Body[1].(uint32)).string()
|
reason := closedReason(s.Body[1].(uint32)).string()
|
||||||
ls.handleNotificationClosed(systemID, reason)
|
ln.handleNotificationClosed(systemID, reason)
|
||||||
case signalActionInvoked:
|
case signalActionInvoked:
|
||||||
systemID := s.Body[0].(uint32)
|
systemID := s.Body[0].(uint32)
|
||||||
actionKey := s.Body[1].(string)
|
actionKey := s.Body[1].(string)
|
||||||
ls.handleActionInvoked(systemID, actionKey)
|
ln.handleActionInvoked(systemID, actionKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleNotificationClosed processes notification closed signals
|
// handleNotificationClosed processes notification closed signals
|
||||||
func (ls *linuxNotifier) handleNotificationClosed(systemID uint32, reason string) {
|
func (ln *linuxNotifier) handleNotificationClosed(systemID uint32, reason string) {
|
||||||
// Find our notification ID for this system ID
|
// Find our notification ID for this system ID
|
||||||
var notifID string
|
var notifID string
|
||||||
var userData map[string]interface{}
|
var userData map[string]interface{}
|
||||||
|
|
||||||
ls.internal.Lock()
|
ln.Lock()
|
||||||
for id, sysID := range ls.internal.activeNotifs {
|
for id, sysID := range ln.activeNotifs {
|
||||||
if sysID == systemID {
|
if sysID == systemID {
|
||||||
notifID = id
|
notifID = id
|
||||||
// Get the user data from context if available
|
// Get the user data from context if available
|
||||||
if ctx, exists := ls.internal.contexts[id]; exists {
|
if ctx, exists := ln.contexts[id]; exists {
|
||||||
userData = ctx.UserData
|
userData = ctx.UserData
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ls.internal.Unlock()
|
ln.Unlock()
|
||||||
|
|
||||||
if notifID != "" {
|
if notifID != "" {
|
||||||
response := NotificationResponse{
|
response := NotificationResponse{
|
||||||
@ -304,28 +317,28 @@ func (ls *linuxNotifier) handleNotificationClosed(systemID uint32, reason string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clean up the context
|
// Clean up the context
|
||||||
ls.internal.Lock()
|
ln.Lock()
|
||||||
delete(ls.internal.contexts, notifID)
|
delete(ln.contexts, notifID)
|
||||||
delete(ls.internal.activeNotifs, notifID)
|
delete(ln.activeNotifs, notifID)
|
||||||
ls.internal.Unlock()
|
ln.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleActionInvoked processes action invoked signals
|
// handleActionInvoked processes action invoked signals
|
||||||
func (ls *linuxNotifier) handleActionInvoked(systemID uint32, actionKey string) {
|
func (ln *linuxNotifier) handleActionInvoked(systemID uint32, actionKey string) {
|
||||||
// Find our notification ID and context for this system ID
|
// Find our notification ID and context for this system ID
|
||||||
var notifID string
|
var notifID string
|
||||||
var ctx *notificationContext
|
var ctx *notificationContext
|
||||||
|
|
||||||
ls.internal.Lock()
|
ln.Lock()
|
||||||
for id, sysID := range ls.internal.activeNotifs {
|
for id, sysID := range ln.activeNotifs {
|
||||||
if sysID == systemID {
|
if sysID == systemID {
|
||||||
notifID = id
|
notifID = id
|
||||||
ctx = ls.internal.contexts[id]
|
ctx = ln.contexts[id]
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ls.internal.Unlock()
|
ln.Unlock()
|
||||||
|
|
||||||
if notifID != "" {
|
if notifID != "" {
|
||||||
if actionKey == "default" {
|
if actionKey == "default" {
|
||||||
@ -372,62 +385,58 @@ func (ls *linuxNotifier) handleActionInvoked(systemID uint32, actionKey string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clean up the context
|
// Clean up the context
|
||||||
ls.internal.Lock()
|
ln.Lock()
|
||||||
delete(ls.internal.contexts, notifID)
|
delete(ln.contexts, notifID)
|
||||||
delete(ls.internal.activeNotifs, notifID)
|
delete(ln.activeNotifs, notifID)
|
||||||
ls.internal.Unlock()
|
ln.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckBundleIdentifier is a Linux stub that always returns true.
|
// CheckBundleIdentifier is a Linux stub that always returns true.
|
||||||
// (bundle identifiers are macOS-specific)
|
func (ln *linuxNotifier) CheckBundleIdentifier() bool {
|
||||||
func (ls *linuxNotifier) CheckBundleIdentifier() bool {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestNotificationAuthorization is a Linux stub that always returns true.
|
// RequestNotificationAuthorization is a Linux stub that always returns true.
|
||||||
// (user authorization is macOS-specific)
|
func (ln *linuxNotifier) RequestNotificationAuthorization() (bool, error) {
|
||||||
func (ls *linuxNotifier) RequestNotificationAuthorization() (bool, error) {
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckNotificationAuthorization is a Linux stub that always returns true.
|
// CheckNotificationAuthorization is a Linux stub that always returns true.
|
||||||
// (user authorization is macOS-specific)
|
func (ln *linuxNotifier) CheckNotificationAuthorization() (bool, error) {
|
||||||
func (ls *linuxNotifier) CheckNotificationAuthorization() (bool, error) {
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendNotification sends a basic notification with a unique identifier, title, subtitle, and body.
|
// SendNotification sends a basic notification with a unique identifier, title, subtitle, and body.
|
||||||
func (ls *linuxNotifier) SendNotification(options NotificationOptions) error {
|
func (ln *linuxNotifier) SendNotification(options NotificationOptions) error {
|
||||||
if ls.internal == nil {
|
if !ln.initialized {
|
||||||
return errors.New("notification service not initialized")
|
return errors.New("notification service not initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
if ls.internal.method == "" || (ls.internal.method == MethodDbus && ls.internal.dbusConn == nil) ||
|
if err := validateNotificationOptions(options); err != nil {
|
||||||
(ls.internal.method == MethodNotifySend && ls.internal.sendPath == "") {
|
return err
|
||||||
return errors.New("notification system not properly initialized")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ls.internal.Lock()
|
ln.Lock()
|
||||||
defer ls.internal.Unlock()
|
defer ln.Unlock()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
systemID uint32
|
systemID uint32
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
switch ls.internal.method {
|
switch ln.method {
|
||||||
case MethodDbus:
|
case MethodDbus:
|
||||||
systemID, err = ls.sendViaDbus(options, nil)
|
systemID, err = ln.sendViaDbus(options, nil)
|
||||||
case MethodNotifySend:
|
case MethodNotifySend:
|
||||||
systemID, err = ls.sendViaNotifySend(options)
|
systemID, err = ln.sendViaNotifySend(options)
|
||||||
default:
|
default:
|
||||||
err = errors.New("no notification method is available")
|
err = errors.New("no notification method is available")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil && systemID > 0 {
|
if err == nil && systemID > 0 {
|
||||||
// Store the system ID mapping
|
// Store the system ID mapping
|
||||||
ls.internal.activeNotifs[options.ID] = systemID
|
ln.activeNotifs[options.ID] = systemID
|
||||||
|
|
||||||
// Create and store the notification context
|
// Create and store the notification context
|
||||||
ctx := ¬ificationContext{
|
ctx := ¬ificationContext{
|
||||||
@ -435,52 +444,51 @@ func (ls *linuxNotifier) SendNotification(options NotificationOptions) error {
|
|||||||
SystemID: systemID,
|
SystemID: systemID,
|
||||||
UserData: options.Data,
|
UserData: options.Data,
|
||||||
}
|
}
|
||||||
ls.internal.contexts[options.ID] = ctx
|
ln.contexts[options.ID] = ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendNotificationWithActions sends a notification with additional actions.
|
// SendNotificationWithActions sends a notification with additional actions.
|
||||||
func (ls *linuxNotifier) SendNotificationWithActions(options NotificationOptions) error {
|
func (ln *linuxNotifier) SendNotificationWithActions(options NotificationOptions) error {
|
||||||
if ls.internal == nil {
|
if !ln.initialized {
|
||||||
return errors.New("notification service not initialized")
|
return errors.New("notification service not initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
if ls.internal.method == "" || (ls.internal.method == MethodDbus && ls.internal.dbusConn == nil) ||
|
if err := validateNotificationOptions(options); err != nil {
|
||||||
(ls.internal.method == MethodNotifySend && ls.internal.sendPath == "") {
|
return err
|
||||||
return errors.New("notification system not properly initialized")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ls.categoriesLock.RLock()
|
ln.categoriesLock.RLock()
|
||||||
category, exists := ls.categories[options.CategoryID]
|
category, exists := ln.categories[options.CategoryID]
|
||||||
ls.categoriesLock.RUnlock()
|
ln.categoriesLock.RUnlock()
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
return ls.SendNotification(options)
|
return ln.SendNotification(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
ls.internal.Lock()
|
ln.Lock()
|
||||||
defer ls.internal.Unlock()
|
defer ln.Unlock()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
systemID uint32
|
systemID uint32
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
switch ls.internal.method {
|
switch ln.method {
|
||||||
case MethodDbus:
|
case MethodDbus:
|
||||||
systemID, err = ls.sendViaDbus(options, &category)
|
systemID, err = ln.sendViaDbus(options, &category)
|
||||||
case MethodNotifySend:
|
case MethodNotifySend:
|
||||||
// notify-send doesn't support actions, fall back to basic notification
|
// notify-send doesn't support actions, fall back to basic notification
|
||||||
systemID, err = ls.sendViaNotifySend(options)
|
systemID, err = ln.sendViaNotifySend(options)
|
||||||
default:
|
default:
|
||||||
err = errors.New("no notification method is available")
|
err = errors.New("no notification method is available")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil && systemID > 0 {
|
if err == nil && systemID > 0 {
|
||||||
// Store the system ID mapping
|
// Store the system ID mapping
|
||||||
ls.internal.activeNotifs[options.ID] = systemID
|
ln.activeNotifs[options.ID] = systemID
|
||||||
|
|
||||||
// Create and store the notification context with actions
|
// Create and store the notification context with actions
|
||||||
ctx := ¬ificationContext{
|
ctx := ¬ificationContext{
|
||||||
@ -497,14 +505,14 @@ func (ls *linuxNotifier) SendNotificationWithActions(options NotificationOptions
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ls.internal.contexts[options.ID] = ctx
|
ln.contexts[options.ID] = ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendViaDbus sends a notification via dbus
|
// sendViaDbus sends a notification via dbus
|
||||||
func (ls *linuxNotifier) sendViaDbus(options NotificationOptions, category *NotificationCategory) (result uint32, err error) {
|
func (ln *linuxNotifier) sendViaDbus(options NotificationOptions, category *NotificationCategory) (result uint32, err error) {
|
||||||
// Prepare actions
|
// Prepare actions
|
||||||
var actions []string
|
var actions []string
|
||||||
if category != nil {
|
if category != nil {
|
||||||
@ -530,9 +538,9 @@ func (ls *linuxNotifier) sendViaDbus(options NotificationOptions, category *Noti
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Send the notification
|
// Send the notification
|
||||||
obj := ls.internal.dbusConn.Object(dbusNotificationsInterface, dbusObjectPath)
|
obj := ln.dbusConn.Object(dbusNotificationsInterface, dbusObjectPath)
|
||||||
dbusArgs := []interface{}{
|
dbusArgs := []interface{}{
|
||||||
ls.appName, // App name
|
ln.appName, // App name
|
||||||
uint32(0), // Replaces ID (0 means new notification)
|
uint32(0), // Replaces ID (0 means new notification)
|
||||||
"", // App icon (empty for now)
|
"", // App icon (empty for now)
|
||||||
options.Title, // Title
|
options.Title, // Title
|
||||||
@ -556,20 +564,15 @@ func (ls *linuxNotifier) sendViaDbus(options NotificationOptions, category *Noti
|
|||||||
}
|
}
|
||||||
|
|
||||||
// sendViaNotifySend sends a notification via notify-send command
|
// sendViaNotifySend sends a notification via notify-send command
|
||||||
func (ls *linuxNotifier) sendViaNotifySend(options NotificationOptions) (uint32, error) {
|
func (ln *linuxNotifier) sendViaNotifySend(options NotificationOptions) (uint32, error) {
|
||||||
args := []string{
|
args := []string{
|
||||||
options.Title,
|
options.Title,
|
||||||
options.Body,
|
options.Body,
|
||||||
|
"--urgency=normal",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add icon if eventually supported
|
|
||||||
// if options.Icon != "" { ... }
|
|
||||||
|
|
||||||
// Add urgency (normal by default)
|
|
||||||
args = append(args, "--urgency=normal")
|
|
||||||
|
|
||||||
// Execute the command
|
// Execute the command
|
||||||
cmd := exec.Command(ls.internal.sendPath, args...)
|
cmd := exec.Command(ln.sendPath, args...)
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("notify-send error: %v", err)
|
return 0, fmt.Errorf("notify-send error: %v", err)
|
||||||
@ -579,78 +582,74 @@ func (ls *linuxNotifier) sendViaNotifySend(options NotificationOptions) (uint32,
|
|||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterNotificationCategory registers a new NotificationCategory to be used with SendNotificationWithActions.
|
// RegisterNotificationCategory registers a new NotificationCategory
|
||||||
func (ls *linuxNotifier) RegisterNotificationCategory(category NotificationCategory) error {
|
func (ln *linuxNotifier) RegisterNotificationCategory(category NotificationCategory) error {
|
||||||
ls.categoriesLock.Lock()
|
ln.categoriesLock.Lock()
|
||||||
ls.categories[category.ID] = category
|
ln.categories[category.ID] = category
|
||||||
ls.categoriesLock.Unlock()
|
ln.categoriesLock.Unlock()
|
||||||
|
|
||||||
return ls.saveCategories()
|
return ln.saveCategories()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveNotificationCategory removes a previously registered NotificationCategory.
|
// RemoveNotificationCategory removes a previously registered NotificationCategory
|
||||||
func (ls *linuxNotifier) RemoveNotificationCategory(categoryId string) error {
|
func (ln *linuxNotifier) RemoveNotificationCategory(categoryId string) error {
|
||||||
ls.categoriesLock.Lock()
|
ln.categoriesLock.Lock()
|
||||||
delete(ls.categories, categoryId)
|
delete(ln.categories, categoryId)
|
||||||
ls.categoriesLock.Unlock()
|
ln.categoriesLock.Unlock()
|
||||||
|
|
||||||
return ls.saveCategories()
|
return ln.saveCategories()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveAllPendingNotifications is a Linux stub that always returns nil.
|
// RemoveAllPendingNotifications is a Linux stub that always returns nil
|
||||||
// (macOS-specific)
|
func (ln *linuxNotifier) RemoveAllPendingNotifications() error {
|
||||||
func (ls *linuxNotifier) RemoveAllPendingNotifications() error {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemovePendingNotification is a Linux stub that always returns nil.
|
// RemovePendingNotification is a Linux stub that always returns nil
|
||||||
// (macOS-specific)
|
func (ln *linuxNotifier) RemovePendingNotification(_ string) error {
|
||||||
func (ls *linuxNotifier) RemovePendingNotification(_ string) error {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveAllDeliveredNotifications is a Linux stub that always returns nil.
|
// RemoveAllDeliveredNotifications is a Linux stub that always returns nil
|
||||||
// (macOS-specific)
|
func (ln *linuxNotifier) RemoveAllDeliveredNotifications() error {
|
||||||
func (ls *linuxNotifier) RemoveAllDeliveredNotifications() error {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveDeliveredNotification is a Linux stub that always returns nil.
|
// RemoveDeliveredNotification is a Linux stub that always returns nil
|
||||||
// (macOS-specific)
|
func (ln *linuxNotifier) RemoveDeliveredNotification(_ string) error {
|
||||||
func (ls *linuxNotifier) RemoveDeliveredNotification(_ string) error {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveNotification removes a notification by ID (Linux-specific)
|
// RemoveNotification removes a notification by ID (Linux-specific)
|
||||||
func (ls *linuxNotifier) RemoveNotification(identifier string) error {
|
func (ln *linuxNotifier) RemoveNotification(identifier string) error {
|
||||||
if ls.internal == nil || ls.internal.method != MethodDbus || ls.internal.dbusConn == nil {
|
if !ln.initialized || ln.method != MethodDbus || ln.dbusConn == nil {
|
||||||
return errors.New("dbus not available for closing notifications")
|
return errors.New("dbus not available for closing notifications")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the system ID for this notification
|
// Get the system ID for this notification
|
||||||
ls.internal.Lock()
|
ln.Lock()
|
||||||
systemID, exists := ls.internal.activeNotifs[identifier]
|
systemID, exists := ln.activeNotifs[identifier]
|
||||||
ls.internal.Unlock()
|
ln.Unlock()
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil // Already closed or unknown
|
return nil // Already closed or unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call CloseNotification on dbus
|
// Call CloseNotification on dbus
|
||||||
obj := ls.internal.dbusConn.Object(dbusNotificationsInterface, dbusObjectPath)
|
obj := ln.dbusConn.Object(dbusNotificationsInterface, dbusObjectPath)
|
||||||
call := obj.Call(callCloseNotification, 0, systemID)
|
call := obj.Call(callCloseNotification, 0, systemID)
|
||||||
|
|
||||||
return call.Err
|
return call.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
// getConfigFilePath returns the path to the configuration file for storing notification categories
|
// getConfigFilePath returns the path to the configuration file
|
||||||
func (ls *linuxNotifier) getConfigFilePath() (string, error) {
|
func (ln *linuxNotifier) getConfigFilePath() (string, error) {
|
||||||
configDir, err := os.UserConfigDir()
|
configDir, err := os.UserConfigDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to get user config directory: %v", err)
|
return "", fmt.Errorf("failed to get user config directory: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
appConfigDir := filepath.Join(configDir, ls.appName)
|
appConfigDir := filepath.Join(configDir, ln.appName)
|
||||||
if err := os.MkdirAll(appConfigDir, 0755); err != nil {
|
if err := os.MkdirAll(appConfigDir, 0755); err != nil {
|
||||||
return "", fmt.Errorf("failed to create config directory: %v", err)
|
return "", fmt.Errorf("failed to create config directory: %v", err)
|
||||||
}
|
}
|
||||||
@ -658,16 +657,16 @@ func (ls *linuxNotifier) getConfigFilePath() (string, error) {
|
|||||||
return filepath.Join(appConfigDir, "notification-categories.json"), nil
|
return filepath.Join(appConfigDir, "notification-categories.json"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// saveCategories saves the notification categories to a file.
|
// saveCategories saves the notification categories to a file
|
||||||
func (ls *linuxNotifier) saveCategories() error {
|
func (ln *linuxNotifier) saveCategories() error {
|
||||||
filePath, err := ls.getConfigFilePath()
|
filePath, err := ln.getConfigFilePath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ls.categoriesLock.RLock()
|
ln.categoriesLock.RLock()
|
||||||
data, err := json.Marshal(ls.categories)
|
data, err := json.Marshal(ln.categories)
|
||||||
ls.categoriesLock.RUnlock()
|
ln.categoriesLock.RUnlock()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal notification categories: %v", err)
|
return fmt.Errorf("failed to marshal notification categories: %v", err)
|
||||||
@ -680,9 +679,9 @@ func (ls *linuxNotifier) saveCategories() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadCategories loads notification categories from a file.
|
// loadCategories loads notification categories from a file
|
||||||
func (ls *linuxNotifier) loadCategories() error {
|
func (ln *linuxNotifier) loadCategories() error {
|
||||||
filePath, err := ls.getConfigFilePath()
|
filePath, err := ln.getConfigFilePath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -705,9 +704,9 @@ func (ls *linuxNotifier) loadCategories() error {
|
|||||||
return fmt.Errorf("failed to unmarshal notification categories: %v", err)
|
return fmt.Errorf("failed to unmarshal notification categories: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ls.categoriesLock.Lock()
|
ln.categoriesLock.Lock()
|
||||||
ls.categories = categories
|
ln.categories = categories
|
||||||
ls.categoriesLock.Unlock()
|
ln.categoriesLock.Unlock()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user