mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-15 07:44:01 -04:00
152 lines
3.3 KiB
Go
152 lines
3.3 KiB
Go
package notifyactions
|
|
|
|
import (
|
|
"os/exec"
|
|
"path/filepath"
|
|
"sync"
|
|
"syscall"
|
|
|
|
"github.com/godbus/dbus/v5"
|
|
|
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
|
)
|
|
|
|
const (
|
|
notifyPath = "/org/freedesktop/Notifications"
|
|
notifyInterface = "org.freedesktop.Notifications"
|
|
actionInvoked = notifyInterface + ".ActionInvoked"
|
|
closed = notifyInterface + ".NotificationClosed"
|
|
dbusInterface = "org.freedesktop.DBus"
|
|
nameOwnerChanged = "NameOwnerChanged"
|
|
ownerChanged = dbusInterface + "." + nameOwnerChanged
|
|
)
|
|
|
|
type watched struct {
|
|
path string
|
|
command []string
|
|
}
|
|
|
|
type Manager struct {
|
|
conn *dbus.Conn
|
|
signals chan *dbus.Signal
|
|
mu sync.Mutex
|
|
watched map[uint32]watched
|
|
}
|
|
|
|
func NewManager() (*Manager, error) {
|
|
conn, err := dbus.SessionBus()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := conn.AddMatchSignal(dbus.WithMatchObjectPath(notifyPath), dbus.WithMatchInterface(notifyInterface)); err != nil {
|
|
return nil, err
|
|
}
|
|
// A restarted notification server reuses ids without sending NotificationClosed.
|
|
if err := conn.AddMatchSignal(dbus.WithMatchInterface(dbusInterface), dbus.WithMatchMember(nameOwnerChanged), dbus.WithMatchArg(0, notifyInterface)); err != nil {
|
|
return nil, err
|
|
}
|
|
m := &Manager{
|
|
conn: conn,
|
|
signals: make(chan *dbus.Signal, 32),
|
|
watched: make(map[uint32]watched),
|
|
}
|
|
conn.Signal(m.signals)
|
|
go m.loop()
|
|
return m, nil
|
|
}
|
|
|
|
func (m *Manager) Watch(id uint32, path string) {
|
|
m.mu.Lock()
|
|
m.watched[id] = watched{path: path}
|
|
m.mu.Unlock()
|
|
}
|
|
|
|
func (m *Manager) WatchCommand(id uint32, argv []string) {
|
|
m.mu.Lock()
|
|
m.watched[id] = watched{command: argv}
|
|
m.mu.Unlock()
|
|
}
|
|
|
|
func (m *Manager) Close() {
|
|
m.conn.RemoveSignal(m.signals)
|
|
_ = m.conn.RemoveMatchSignal(dbus.WithMatchObjectPath(notifyPath), dbus.WithMatchInterface(notifyInterface))
|
|
_ = m.conn.RemoveMatchSignal(dbus.WithMatchInterface(dbusInterface), dbus.WithMatchMember(nameOwnerChanged), dbus.WithMatchArg(0, notifyInterface))
|
|
close(m.signals)
|
|
}
|
|
|
|
func (m *Manager) loop() {
|
|
for sig := range m.signals {
|
|
switch sig.Name {
|
|
case actionInvoked:
|
|
m.handleAction(sig)
|
|
case closed:
|
|
m.forget(sig)
|
|
case ownerChanged:
|
|
// The shared bus connection also delivers other managers' NameOwnerChanged matches.
|
|
if len(sig.Body) < 1 || sig.Body[0] != notifyInterface {
|
|
continue
|
|
}
|
|
m.mu.Lock()
|
|
clear(m.watched)
|
|
m.mu.Unlock()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (m *Manager) take(sig *dbus.Signal) (watched, bool) {
|
|
if len(sig.Body) < 1 {
|
|
return watched{}, false
|
|
}
|
|
id, ok := sig.Body[0].(uint32)
|
|
if !ok {
|
|
return watched{}, false
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
w, ok := m.watched[id]
|
|
if !ok {
|
|
return watched{}, false
|
|
}
|
|
delete(m.watched, id)
|
|
return w, true
|
|
}
|
|
|
|
func (m *Manager) forget(sig *dbus.Signal) {
|
|
m.take(sig)
|
|
}
|
|
|
|
func (m *Manager) handleAction(sig *dbus.Signal) {
|
|
if len(sig.Body) < 2 {
|
|
return
|
|
}
|
|
action, ok := sig.Body[1].(string)
|
|
if !ok {
|
|
return
|
|
}
|
|
w, ok := m.take(sig)
|
|
if !ok {
|
|
return
|
|
}
|
|
if len(w.command) > 0 {
|
|
if action == "default" {
|
|
run(w.command)
|
|
}
|
|
return
|
|
}
|
|
path := w.path
|
|
if action == "folder" {
|
|
path = filepath.Dir(path)
|
|
}
|
|
run([]string{"xdg-open", path})
|
|
}
|
|
|
|
func run(argv []string) {
|
|
cmd := exec.Command(argv[0], argv[1:]...)
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
|
if err := cmd.Start(); err != nil {
|
|
log.Warnf("notifyactions: %v: %v", argv, err)
|
|
return
|
|
}
|
|
go func() { _ = cmd.Wait() }()
|
|
}
|