diff --git a/core/cmd/dms/commands_notify.go b/core/cmd/dms/commands_notify.go index e04b60f1b..a10e7e02d 100644 --- a/core/cmd/dms/commands_notify.go +++ b/core/cmd/dms/commands_notify.go @@ -5,6 +5,7 @@ import ( "os" "github.com/AvengeMedia/DankMaterialShell/core/internal/notify" + "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" "github.com/spf13/cobra" ) @@ -61,8 +62,27 @@ func runNotify(cmd *cobra.Command, args []string) { Timeout: int32(notifyTimeout), } - if err := notify.Send(n); err != nil { + id, err := notify.Send(n) + if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } + if n.FilePath == "" { + return + } + watchNotificationAction(id, n.FilePath) +} + +func watchNotificationAction(id uint32, path string) { + if id == 0 { + return + } + resp, ok := tryServerRequest(models.Request{ + Method: "notify.watchAction", + Params: map[string]any{"id": id, "path": path}, + }) + if ok && resp.Error == "" { + return + } + notify.SpawnActionListener(id, path) } diff --git a/core/cmd/dms/commands_screenshot.go b/core/cmd/dms/commands_screenshot.go index 5678bf230..dd7d7298b 100644 --- a/core/cmd/dms/commands_screenshot.go +++ b/core/cmd/dms/commands_screenshot.go @@ -13,6 +13,7 @@ import ( "strings" "github.com/AvengeMedia/DankMaterialShell/core/internal/clipboard" + "github.com/AvengeMedia/DankMaterialShell/core/internal/notify" "github.com/AvengeMedia/DankMaterialShell/core/internal/screenshot" "github.com/spf13/cobra" ) @@ -152,7 +153,7 @@ var notifyActionCmd = &cobra.Command{ Use: "notify-action", Hidden: true, Run: func(cmd *cobra.Command, args []string) { - screenshot.RunNotifyActionListener(args) + notify.RunActionListener(args) }, } @@ -358,13 +359,14 @@ func runScreenshot(config screenshot.Config) { if config.Notify { thumbData, thumbW, thumbH := bufferToRGBThumbnail(result.Buffer, 256, result.Format) - screenshot.SendNotification(screenshot.NotifyResult{ + id := screenshot.SendNotification(screenshot.NotifyResult{ FilePath: filePath, Clipboard: config.Clipboard, ImageData: thumbData, Width: thumbW, Height: thumbH, }) + watchNotificationAction(id, filePath) } } diff --git a/core/cmd/dms/main.go b/core/cmd/dms/main.go index ec865a4ab..208c9117b 100644 --- a/core/cmd/dms/main.go +++ b/core/cmd/dms/main.go @@ -26,6 +26,7 @@ func init() { } func main() { + disableMemProfilingUnlessRequested() clipboard.MaybeServeAndExit() if os.Geteuid() == 0 && !isReadOnlyCommand(os.Args) { diff --git a/core/cmd/dms/main_distro.go b/core/cmd/dms/main_distro.go index 783e446b3..35d1ba65a 100644 --- a/core/cmd/dms/main_distro.go +++ b/core/cmd/dms/main_distro.go @@ -22,6 +22,7 @@ func init() { } func main() { + disableMemProfilingUnlessRequested() clipboard.MaybeServeAndExit() if os.Geteuid() == 0 && !isReadOnlyCommand(os.Args) { diff --git a/core/cmd/dms/utils.go b/core/cmd/dms/utils.go index 20d1f34d1..bd24585f4 100644 --- a/core/cmd/dms/utils.go +++ b/core/cmd/dms/utils.go @@ -1,10 +1,19 @@ package main import ( + "os" "os/exec" + "runtime" "strings" ) +func disableMemProfilingUnlessRequested() { + if os.Getenv("DMS_PPROF") != "" { + return + } + runtime.MemProfileRate = 0 +} + // isReadOnlyCommand returns true if the CLI args indicate a command that is // safe to run as root (e.g. shell completion, help). func isReadOnlyCommand(args []string) bool { diff --git a/core/internal/geolocation/client_geoclue.go b/core/internal/geolocation/client_geoclue.go index ffbdaadb3..b77bd237c 100644 --- a/core/internal/geolocation/client_geoclue.go +++ b/core/internal/geolocation/client_geoclue.go @@ -47,7 +47,7 @@ type GeoClueClient struct { } func newGeoClueClient() (*GeoClueClient, error) { - dbusConn, err := dbus.ConnectSystemBus() + dbusConn, err := dbus.SystemBus() if err != nil { return nil, fmt.Errorf("system bus connection failed: %w", err) } @@ -64,7 +64,6 @@ func newGeoClueClient() (*GeoClueClient, error) { } if err := c.setupClient(); err != nil { - dbusConn.Close() return nil, err } @@ -90,10 +89,6 @@ func (c *GeoClueClient) Close() { c.subscribers.Delete(key) return true }) - - if c.dbusConn != nil { - c.dbusConn.Close() - } } func (c *GeoClueClient) Subscribe(id string) chan Location { diff --git a/core/internal/notify/notify.go b/core/internal/notify/notify.go index 1e42b1694..610a11dba 100644 --- a/core/internal/notify/notify.go +++ b/core/internal/notify/notify.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" "syscall" + "time" "github.com/godbus/dbus/v5" ) @@ -19,6 +20,8 @@ const ( maxSummaryLen = 29 maxBodyLen = 80 + + listenerMaxLifetime = time.Hour ) type Notification struct { @@ -30,10 +33,10 @@ type Notification struct { Timeout int32 } -func Send(n Notification) error { +func Send(n Notification) (uint32, error) { conn, err := dbus.SessionBus() if err != nil { - return fmt.Errorf("dbus session failed: %w", err) + return 0, fmt.Errorf("dbus session failed: %w", err) } if n.AppName == "" { @@ -82,22 +85,18 @@ func Send(n Notification) error { ) if call.Err != nil { - return fmt.Errorf("notify call failed: %w", call.Err) + return 0, fmt.Errorf("notify call failed: %w", call.Err) } var notificationID uint32 if err := call.Store(¬ificationID); err != nil { - return fmt.Errorf("failed to get notification id: %w", err) + return 0, fmt.Errorf("failed to get notification id: %w", err) } - if len(actions) > 0 && n.FilePath != "" { - spawnActionListener(notificationID, n.FilePath) - } - - return nil + return notificationID, nil } -func spawnActionListener(notificationID uint32, filePath string) { +func SpawnActionListener(notificationID uint32, filePath string) { exe, err := os.Executable() if err != nil { return @@ -136,37 +135,45 @@ func RunActionListener(args []string) { signals := make(chan *dbus.Signal, 10) conn.Signal(signals) + deadline := time.After(listenerMaxLifetime) - for sig := range signals { - switch sig.Name { - case notifyInterface + ".ActionInvoked": - if len(sig.Body) < 2 { - continue - } - id, ok := sig.Body[0].(uint32) - if !ok || id != uint32(notificationID) { - continue - } - action, ok := sig.Body[1].(string) - if !ok { - continue - } - handleAction(action, filePath) + for { + select { + case <-deadline: return - - case notifyInterface + ".NotificationClosed": - if len(sig.Body) < 1 { - continue + case sig := <-signals: + if sig == nil || handleSignal(sig, uint32(notificationID), filePath) { + return } - id, ok := sig.Body[0].(uint32) - if !ok || id != uint32(notificationID) { - continue - } - return } } } +func handleSignal(sig *dbus.Signal, notificationID uint32, filePath string) bool { + if len(sig.Body) < 1 { + return false + } + id, ok := sig.Body[0].(uint32) + if !ok || id != notificationID { + return false + } + switch sig.Name { + case notifyInterface + ".NotificationClosed": + return true + case notifyInterface + ".ActionInvoked": + if len(sig.Body) < 2 { + return false + } + action, ok := sig.Body[1].(string) + if !ok { + return false + } + handleAction(action, filePath) + return true + } + return false +} + func handleAction(action, filePath string) { switch action { case "open", "default": diff --git a/core/internal/screenshot/notify.go b/core/internal/screenshot/notify.go index 2aaa9f6c6..322b689ab 100644 --- a/core/internal/screenshot/notify.go +++ b/core/internal/screenshot/notify.go @@ -2,11 +2,7 @@ package screenshot import ( "fmt" - "os" - "os/exec" "path/filepath" - "strconv" - "syscall" "github.com/AvengeMedia/DankMaterialShell/core/internal/log" "github.com/godbus/dbus/v5" @@ -26,11 +22,11 @@ type NotifyResult struct { Height int } -func SendNotification(result NotifyResult) { +func SendNotification(result NotifyResult) uint32 { conn, err := dbus.SessionBus() if err != nil { log.Debug("dbus session failed", "err", err) - return + return 0 } var actions []string @@ -89,93 +85,17 @@ func SendNotification(result NotifyResult) { if call.Err != nil { log.Debug("notify call failed", "err", call.Err) - return + return 0 } var notificationID uint32 if err := call.Store(¬ificationID); err != nil { log.Debug("failed to get notification id", "err", err) - return + return 0 } - if len(actions) == 0 || result.FilePath == "" { - return + if len(actions) == 0 { + return 0 } - - spawnActionListener(notificationID, result.FilePath) -} - -func spawnActionListener(notificationID uint32, filePath string) { - exe, err := os.Executable() - if err != nil { - log.Debug("failed to get executable", "err", err) - return - } - - cmd := exec.Command(exe, "notify-action", fmt.Sprintf("%d", notificationID), filePath) - cmd.SysProcAttr = &syscall.SysProcAttr{ - Setsid: true, - } - cmd.Start() -} - -func RunNotifyActionListener(args []string) { - if len(args) < 2 { - return - } - - notificationID, err := strconv.ParseUint(args[0], 10, 32) - if err != nil { - return - } - - filePath := args[1] - - conn, err := dbus.SessionBus() - if err != nil { - return - } - - if err := conn.AddMatchSignal( - dbus.WithMatchObjectPath(notifyPath), - dbus.WithMatchInterface(notifyInterface), - ); err != nil { - return - } - - signals := make(chan *dbus.Signal, 10) - conn.Signal(signals) - - for sig := range signals { - switch sig.Name { - case notifyInterface + ".ActionInvoked": - if len(sig.Body) < 2 { - continue - } - id, ok := sig.Body[0].(uint32) - if !ok || id != uint32(notificationID) { - continue - } - openFile(filePath) - return - - case notifyInterface + ".NotificationClosed": - if len(sig.Body) < 1 { - continue - } - id, ok := sig.Body[0].(uint32) - if !ok || id != uint32(notificationID) { - continue - } - return - } - } -} - -func openFile(filePath string) { - cmd := exec.Command("xdg-open", filePath) - cmd.SysProcAttr = &syscall.SysProcAttr{ - Setsid: true, - } - cmd.Start() + return notificationID } diff --git a/core/internal/server/bluez/agent.go b/core/internal/server/bluez/agent.go index b630a1956..d6281fcad 100644 --- a/core/internal/server/bluez/agent.go +++ b/core/internal/server/bluez/agent.go @@ -69,7 +69,7 @@ type BluezAgent struct { } func NewBluezAgent(broker PromptBroker) (*BluezAgent, error) { - conn, err := dbus.ConnectSystemBus() + conn, err := dbus.SystemBus() if err != nil { return nil, fmt.Errorf("system bus connection failed: %w", err) } @@ -80,18 +80,17 @@ func NewBluezAgent(broker PromptBroker) (*BluezAgent, error) { } if err := conn.Export(agent, dbus.ObjectPath(agentPath), agent1Iface); err != nil { - conn.Close() return nil, fmt.Errorf("agent export failed: %w", err) } if err := conn.Export(agent, dbus.ObjectPath(agentPath), "org.freedesktop.DBus.Introspectable"); err != nil { - conn.Close() + agent.unexport() return nil, fmt.Errorf("introspection export failed: %w", err) } mgr := conn.Object(bluezService, dbus.ObjectPath(agentManagerPath)) if err := mgr.Call(agentManagerIface+".RegisterAgent", 0, dbus.ObjectPath(agentPath), agentCapability).Err; err != nil { - conn.Close() + agent.unexport() return nil, fmt.Errorf("agent registration failed: %w", err) } @@ -109,7 +108,12 @@ func (a *BluezAgent) Close() { } mgr := a.conn.Object(bluezService, dbus.ObjectPath(agentManagerPath)) mgr.Call(agentManagerIface+".UnregisterAgent", 0, dbus.ObjectPath(agentPath)) - a.conn.Close() + a.unexport() +} + +func (a *BluezAgent) unexport() { + _ = a.conn.Export(nil, dbus.ObjectPath(agentPath), agent1Iface) + _ = a.conn.Export(nil, dbus.ObjectPath(agentPath), "org.freedesktop.DBus.Introspectable") } func (a *BluezAgent) Release() *dbus.Error { diff --git a/core/internal/server/bluez/manager.go b/core/internal/server/bluez/manager.go index 004617bef..c63f459c1 100644 --- a/core/internal/server/bluez/manager.go +++ b/core/internal/server/bluez/manager.go @@ -18,8 +18,14 @@ const ( propertiesIface = "org.freedesktop.DBus.Properties" ) +var bluezMatchRules = [][]dbus.MatchOption{ + {dbus.WithMatchInterface(propertiesIface), dbus.WithMatchMember("PropertiesChanged")}, + {dbus.WithMatchInterface(objectMgrIface), dbus.WithMatchMember("InterfacesAdded")}, + {dbus.WithMatchInterface(objectMgrIface), dbus.WithMatchMember("InterfacesRemoved")}, +} + func NewManager() (*Manager, error) { - conn, err := dbus.ConnectSystemBus() + conn, err := dbus.SystemBus() if err != nil { return nil, fmt.Errorf("system bus connection failed: %w", err) } @@ -47,23 +53,19 @@ func NewManager() (*Manager, error) { adapters, err := scanAdapters(conn) if err != nil { - conn.Close() return nil, fmt.Errorf("%w: %v", ErrNoAdapter, err) } if len(adapters) == 0 { - conn.Close() return nil, ErrNoAdapter } m.adapterPaths = adapters log.Infof("[BluezManager] adapters: %v (default: %s)", adapters, adapters[0]) if err := m.initialize(); err != nil { - conn.Close() return nil, err } if err := m.startAgent(); err != nil { - conn.Close() return nil, fmt.Errorf("agent start failed: %w", err) } @@ -277,25 +279,10 @@ func (m *Manager) startAgent() error { func (m *Manager) startSignalPump() error { m.dbusConn.Signal(m.signals) - if err := m.dbusConn.AddMatchSignal( - dbus.WithMatchInterface(propertiesIface), - dbus.WithMatchMember("PropertiesChanged"), - ); err != nil { - return err - } - - if err := m.dbusConn.AddMatchSignal( - dbus.WithMatchInterface(objectMgrIface), - dbus.WithMatchMember("InterfacesAdded"), - ); err != nil { - return err - } - - if err := m.dbusConn.AddMatchSignal( - dbus.WithMatchInterface(objectMgrIface), - dbus.WithMatchMember("InterfacesRemoved"), - ); err != nil { - return err + for _, rule := range bluezMatchRules { + if err := m.dbusConn.AddMatchSignal(rule...); err != nil { + return err + } } m.sigWG.Go(func() { @@ -673,6 +660,9 @@ func (m *Manager) Close() { m.dbusConn.RemoveSignal(m.signals) close(m.signals) } + for _, rule := range bluezMatchRules { + _ = m.dbusConn.RemoveMatchSignal(rule...) + } if m.agent != nil { m.agent.Close() @@ -689,10 +679,6 @@ func (m *Manager) Close() { m.pairingSubscribers.Delete(key) return true }) - - if m.dbusConn != nil { - m.dbusConn.Close() - } } func stateChanged(old, new *BluetoothState) bool { diff --git a/core/internal/server/bluez/watcher.go b/core/internal/server/bluez/watcher.go index a7523c618..71c0ff5db 100644 --- a/core/internal/server/bluez/watcher.go +++ b/core/internal/server/bluez/watcher.go @@ -10,21 +10,20 @@ import ( var ErrNoAdapter = errors.New("no bluetooth adapter found") func WaitForAdapter() error { - conn, err := dbus.ConnectSystemBus() + conn, err := dbus.SystemBus() if err != nil { return err } - defer conn.Close() - - if err := conn.AddMatchSignal( - dbus.WithMatchInterface(objectMgrIface), - dbus.WithMatchMember("InterfacesAdded"), - ); err != nil { - return err - } signals := make(chan *dbus.Signal, 64) conn.Signal(signals) + defer conn.RemoveSignal(signals) + + adapterAdded := []dbus.MatchOption{dbus.WithMatchInterface(objectMgrIface), dbus.WithMatchMember("InterfacesAdded")} + if err := conn.AddMatchSignal(adapterAdded...); err != nil { + return err + } + defer conn.RemoveMatchSignal(adapterAdded...) if paths, err := scanAdapters(conn); err == nil && len(paths) > 0 { return nil diff --git a/core/internal/server/brightness/logind.go b/core/internal/server/brightness/logind.go index 8449ab5f2..4ab4950ba 100644 --- a/core/internal/server/brightness/logind.go +++ b/core/internal/server/brightness/logind.go @@ -9,7 +9,6 @@ import ( type DBusConn interface { Object(dest string, path dbus.ObjectPath) dbus.BusObject - Close() error } type LogindBackend struct { @@ -17,7 +16,7 @@ type LogindBackend struct { } func NewLogindBackend() (*LogindBackend, error) { - conn, err := dbus.ConnectSystemBus() + conn, err := dbus.SystemBus() if err != nil { return nil, fmt.Errorf("connect to system bus: %w", err) } @@ -25,13 +24,10 @@ func NewLogindBackend() (*LogindBackend, error) { obj := conn.Object("org.freedesktop.login1", "/org/freedesktop/login1/session/auto") call := obj.Call("org.freedesktop.DBus.Peer.Ping", 0) if call.Err != nil { - conn.Close() return nil, fmt.Errorf("logind not available: %w", call.Err) } - conn.Close() - - return &LogindBackend{}, nil + return &LogindBackend{conn: conn}, nil } func NewLogindBackendWithConn(conn DBusConn) *LogindBackend { @@ -42,7 +38,7 @@ func NewLogindBackendWithConn(conn DBusConn) *LogindBackend { func (b *LogindBackend) SetBrightness(subsystem, name string, brightness uint32) error { if b.conn == nil { - conn, err := dbus.ConnectSystemBus() + conn, err := dbus.SystemBus() if err != nil { return fmt.Errorf("connect to system bus: %w", err) } @@ -59,8 +55,4 @@ func (b *LogindBackend) SetBrightness(subsystem, name string, brightness uint32) return nil } -func (b *LogindBackend) Close() { - if b.conn != nil { - b.conn.Close() - } -} +func (b *LogindBackend) Close() {} diff --git a/core/internal/server/brightness/logind_test.go b/core/internal/server/brightness/logind_test.go index 3d4c2eeb6..bd8012eb1 100644 --- a/core/internal/server/brightness/logind_test.go +++ b/core/internal/server/brightness/logind_test.go @@ -77,18 +77,6 @@ func TestLogindBackend_SetBrightness_LEDDevice(t *testing.T) { } } -func TestLogindBackend_Close(t *testing.T) { - mockConn := mocks_brightness.NewMockDBusConn(t) - backend := NewLogindBackendWithConn(mockConn) - - mockConn.EXPECT(). - Close(). - Return(nil). - Once() - - backend.Close() -} - func TestLogindBackend_Close_NilConn(t *testing.T) { backend := &LogindBackend{conn: nil} backend.Close() diff --git a/core/internal/server/clipboard/manager.go b/core/internal/server/clipboard/manager.go index 07d71aa01..44e1ef55f 100644 --- a/core/internal/server/clipboard/manager.go +++ b/core/internal/server/clipboard/manager.go @@ -13,6 +13,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime/debug" "slices" "strings" "syscall" @@ -347,10 +348,10 @@ func (m *Manager) releaseCurrentSource() { source.Destroy() } -func readPipeTimeout(r *os.File) []byte { +func readPipeTimeout(r *os.File, maxSize int64) []byte { done := make(chan []byte, 1) go func() { - data, _ := io.ReadAll(r) + data, _ := io.ReadAll(io.LimitReader(r, maxSize+1)) done <- data }() @@ -374,11 +375,11 @@ func (m *Manager) readAndStore(r *os.File, mimeType string, altR *os.File, altMi default: go func() { defer altR.Close() - altCh <- readPipeTimeout(altR) + altCh <- readPipeTimeout(altR, cfg.MaxEntrySize) }() } - data := readPipeTimeout(r) + data := readPipeTimeout(r, cfg.MaxEntrySize) altData := <-altCh if len(bytes.TrimSpace(altData)) == 0 || int64(len(altData)) > cfg.MaxEntrySize { @@ -398,6 +399,10 @@ func (m *Manager) readAndStore(r *os.File, mimeType string, altR *os.File, altMi m.updateState() m.notifySubscribers() + + if len(data)+len(altData) >= largeEntryBytes { + debug.FreeOSMemory() + } } func (m *Manager) storeClipboardEntry(data []byte, mimeType string, altData []byte, altMime string) { @@ -1964,12 +1969,11 @@ func (m *Manager) dbusConnForFlatpak() (*dbus.Conn, error) { return m.dbusConn, nil } - conn, err := dbus.ConnectSessionBus() + conn, err := dbus.SessionBus() if err != nil { return nil, fmt.Errorf("connect session bus: %w", err) } if !conn.SupportsUnixFDs() { - conn.Close() return nil, fmt.Errorf("D-Bus connection does not support Unix FD passing") } m.dbusConn = conn diff --git a/core/internal/server/clipboard/types.go b/core/internal/server/clipboard/types.go index 61f3963cf..58ce32efd 100644 --- a/core/internal/server/clipboard/types.go +++ b/core/internal/server/clipboard/types.go @@ -14,6 +14,8 @@ import ( wlclient "github.com/AvengeMedia/dankgo/wayland/client" ) +const largeEntryBytes = 1 << 20 + type Config struct { MaxHistory int `json:"maxHistory"` MaxEntrySize int64 `json:"maxEntrySize"` diff --git a/core/internal/server/cups/subscription_dbus.go b/core/internal/server/cups/subscription_dbus.go index 951e54984..4fc54dae4 100644 --- a/core/internal/server/cups/subscription_dbus.go +++ b/core/internal/server/cups/subscription_dbus.go @@ -10,6 +10,8 @@ import ( "github.com/godbus/dbus/v5" ) +var cupsNotifierMatch = dbus.WithMatchInterface("org.cups.cupsd.Notifier") + type DBusSubscriptionManager struct { client CUPSClientInterface subscriptionID int @@ -42,7 +44,7 @@ func (sm *DBusSubscriptionManager) Start() error { sm.eventChan = make(chan SubscriptionEvent, 100) sm.mu.Unlock() - conn, err := dbus.ConnectSystemBus() + conn, err := dbus.SystemBus() if err != nil { sm.mu.Lock() sm.running = false @@ -53,7 +55,6 @@ func (sm *DBusSubscriptionManager) Start() error { subID, err := sm.createDBusSubscription() if err != nil { - sm.conn.Close() sm.mu.Lock() sm.running = false sm.mu.Unlock() @@ -63,11 +64,8 @@ func (sm *DBusSubscriptionManager) Start() error { sm.subscriptionID = subID log.Infof("[CUPS] Created D-Bus subscription with ID %d", subID) - if err := sm.conn.AddMatchSignal( - dbus.WithMatchInterface("org.cups.cupsd.Notifier"), - ); err != nil { + if err := sm.conn.AddMatchSignal(cupsNotifierMatch); err != nil { sm.cancelSubscription() - sm.conn.Close() sm.mu.Lock() sm.running = false sm.mu.Unlock() @@ -277,7 +275,7 @@ func (sm *DBusSubscriptionManager) Stop() { } if sm.conn != nil { - sm.conn.Close() + _ = sm.conn.RemoveMatchSignal(cupsNotifierMatch) sm.conn = nil } diff --git a/core/internal/server/dbus/manager.go b/core/internal/server/dbus/manager.go index 994789d9d..8297dd31d 100644 --- a/core/internal/server/dbus/manager.go +++ b/core/internal/server/dbus/manager.go @@ -12,20 +12,21 @@ import ( ) func NewManager() (*Manager, error) { - systemConn, err := dbus.ConnectSystemBus() + systemConn, err := dbus.SystemBus() if err != nil { return nil, fmt.Errorf("failed to connect to system bus: %w", err) } - sessionConn, err := dbus.ConnectSessionBus() + sessionConn, err := dbus.SessionBus() if err != nil { - systemConn.Close() return nil, fmt.Errorf("failed to connect to session bus: %w", err) } m := &Manager{ - systemConn: systemConn, - sessionConn: sessionConn, + systemConn: systemConn, + sessionConn: sessionConn, + systemSignals: make(chan *dbus.Signal, 256), + sessionSignals: make(chan *dbus.Signal, 256), } go m.processSystemSignals() @@ -300,10 +301,9 @@ func (m *Manager) processSystemSignals() { if m.systemConn == nil { return } - ch := make(chan *dbus.Signal, 256) - m.systemConn.Signal(ch) + m.systemConn.Signal(m.systemSignals) - for sig := range ch { + for sig := range m.systemSignals { m.dispatchSignal("system", sig) } } @@ -312,10 +312,9 @@ func (m *Manager) processSessionSignals() { if m.sessionConn == nil { return } - ch := make(chan *dbus.Signal, 256) - m.sessionConn.Signal(ch) + m.sessionConn.Signal(m.sessionSignals) - for sig := range ch { + for sig := range m.sessionSignals { m.dispatchSignal("session", sig) } } @@ -376,10 +375,12 @@ func (m *Manager) Close() { }) if m.systemConn != nil { - m.systemConn.Close() + m.systemConn.RemoveSignal(m.systemSignals) + close(m.systemSignals) } if m.sessionConn != nil { - m.sessionConn.Close() + m.sessionConn.RemoveSignal(m.sessionSignals) + close(m.sessionSignals) } } diff --git a/core/internal/server/dbus/types.go b/core/internal/server/dbus/types.go index dcac8a239..762d32df5 100644 --- a/core/internal/server/dbus/types.go +++ b/core/internal/server/dbus/types.go @@ -6,8 +6,10 @@ import ( ) type Manager struct { - systemConn *dbus.Conn - sessionConn *dbus.Conn + systemConn *dbus.Conn + sessionConn *dbus.Conn + systemSignals chan *dbus.Signal + sessionSignals chan *dbus.Signal subscriptions syncmap.Map[string, *signalSubscription] signalSubscribers syncmap.Map[string, chan SignalEvent] diff --git a/core/internal/server/evdev/manager.go b/core/internal/server/evdev/manager.go index 47d98f024..c9831d10a 100644 --- a/core/internal/server/evdev/manager.go +++ b/core/internal/server/evdev/manager.go @@ -85,6 +85,16 @@ func NewManager() (*Manager, error) { return m, nil } +func armNonBlocking(device EvdevDevice) { + nb, ok := device.(interface{ NonBlock() error }) + if !ok { + return + } + if err := nb.NonBlock(); err != nil { + log.Debugf("evdev: non-blocking mode for %s: %v", device.Path(), err) + } +} + func capsLockFromDevices(devices []EvdevDevice) (bool, bool) { for _, device := range devices { if device == nil { @@ -92,6 +102,7 @@ func capsLockFromDevices(devices []EvdevDevice) (bool, bool) { } ledStates, err := device.State(evLedType) + armNonBlocking(device) if err != nil || len(ledStates) == 0 { continue } @@ -126,6 +137,7 @@ func findKeyboards() ([]EvdevDevice, error) { } deviceName, _ := device.Name() + armNonBlocking(device) log.Debugf("Found keyboard: %s at %s", deviceName, path) keyboards = append(keyboards, device) } @@ -208,6 +220,7 @@ func (m *Manager) watchForNewKeyboards() { } deviceName, _ := device.Name() + armNonBlocking(device) log.Debugf("Hotplugged keyboard: %s at %s", deviceName, event.Name) m.devices = append(m.devices, device) diff --git a/core/internal/server/freedesktop/manager.go b/core/internal/server/freedesktop/manager.go index e8e281505..0dc7ade50 100644 --- a/core/internal/server/freedesktop/manager.go +++ b/core/internal/server/freedesktop/manager.go @@ -13,12 +13,12 @@ import ( ) func NewManager() (*Manager, error) { - systemConn, err := dbus.ConnectSystemBus() + systemConn, err := dbus.SystemBus() if err != nil { return nil, fmt.Errorf("failed to connect to system bus: %w", err) } - sessionConn, err := dbus.ConnectSessionBus() + sessionConn, err := dbus.SessionBus() if err != nil { sessionConn = nil } @@ -308,9 +308,6 @@ func (m *Manager) Close() { return true }) - if m.systemConn != nil { - m.systemConn.Close() - } if m.sessionConn != nil { m.sessionConn.RemoveMatchSignal( dbus.WithMatchInterface(dbusPortalSettingsInterface), @@ -324,6 +321,5 @@ func (m *Manager) Close() { m.sessionConn.RemoveSignal(signals) close(signals) } - m.sessionConn.Close() } } diff --git a/core/internal/server/loginctl/manager.go b/core/internal/server/loginctl/manager.go index e2ec58fad..3537458a0 100644 --- a/core/internal/server/loginctl/manager.go +++ b/core/internal/server/loginctl/manager.go @@ -12,7 +12,7 @@ import ( ) func NewManager() (*Manager, error) { - conn, err := dbus.ConnectSystemBus() + conn, err := dbus.SystemBus() if err != nil { return nil, fmt.Errorf("failed to connect to system bus: %w", err) } @@ -29,7 +29,6 @@ func NewManager() (*Manager, error) { m.sleepInhibitorEnabled.Store(true) if err := m.initialize(); err != nil { - conn.Close() return nil, err } @@ -714,8 +713,4 @@ func (m *Manager) Close() { m.subscribers.Delete(key) return true }) - - if m.conn != nil { - m.conn.Close() - } } diff --git a/core/internal/server/network/agent_iwd.go b/core/internal/server/network/agent_iwd.go index 01a76899f..861516104 100644 --- a/core/internal/server/network/agent_iwd.go +++ b/core/internal/server/network/agent_iwd.go @@ -92,10 +92,13 @@ func NewIWDAgent(conn *dbus.Conn, prompts PromptBroker) (*IWDAgent, error) { } func (a *IWDAgent) Close() { - if a.conn != nil { - mgr := a.conn.Object("net.connman.iwd", dbus.ObjectPath(iwdAgentManagerPath)) - mgr.Call(iwdAgentManagerIface+".UnregisterAgent", 0, a.objPath) + if a.conn == nil { + return } + mgr := a.conn.Object("net.connman.iwd", dbus.ObjectPath(iwdAgentManagerPath)) + mgr.Call(iwdAgentManagerIface+".UnregisterAgent", 0, a.objPath) + _ = a.conn.Export(nil, a.objPath, iwdAgentInterface) + _ = a.conn.Export(nil, a.objPath, "org.freedesktop.DBus.Introspectable") } func (a *IWDAgent) SetStateChecker(checker ConnectionStateChecker) { diff --git a/core/internal/server/network/agent_networkmanager.go b/core/internal/server/network/agent_networkmanager.go index 8713a0342..756627f7c 100644 --- a/core/internal/server/network/agent_networkmanager.go +++ b/core/internal/server/network/agent_networkmanager.go @@ -75,7 +75,7 @@ const introspectXML = ` ` func NewSecretAgent(prompts PromptBroker, manager *Manager, backend *NetworkManagerBackend) (*SecretAgent, error) { - c, err := dbus.ConnectSystemBus() + c, err := dbus.SystemBus() if err != nil { return nil, fmt.Errorf("failed to connect to system bus: %w", err) } @@ -90,19 +90,18 @@ func NewSecretAgent(prompts PromptBroker, manager *Manager, backend *NetworkMana } if err := c.Export(sa, sa.objPath, nmSecretAgentIface); err != nil { - c.Close() return nil, fmt.Errorf("failed to export secret agent: %w", err) } if err := c.Export(sa, sa.objPath, "org.freedesktop.DBus.Introspectable"); err != nil { - c.Close() + sa.unexport() return nil, fmt.Errorf("failed to export introspection: %w", err) } mgr := c.Object("org.freedesktop.NetworkManager", dbus.ObjectPath(nmAgentManagerPath)) call := mgr.Call(nmAgentManagerIface+".Register", 0, sa.id) if call.Err != nil { - c.Close() + sa.unexport() return nil, fmt.Errorf("failed to register agent with NetworkManager: %w", call.Err) } @@ -111,11 +110,17 @@ func NewSecretAgent(prompts PromptBroker, manager *Manager, backend *NetworkMana } func (a *SecretAgent) Close() { - if a.conn != nil { - mgr := a.conn.Object("org.freedesktop.NetworkManager", dbus.ObjectPath(nmAgentManagerPath)) - mgr.Call(nmAgentManagerIface+".Unregister", 0, a.id) - a.conn.Close() + if a.conn == nil { + return } + mgr := a.conn.Object("org.freedesktop.NetworkManager", dbus.ObjectPath(nmAgentManagerPath)) + mgr.Call(nmAgentManagerIface+".Unregister", 0, a.id) + a.unexport() +} + +func (a *SecretAgent) unexport() { + _ = a.conn.Export(nil, a.objPath, nmSecretAgentIface) + _ = a.conn.Export(nil, a.objPath, "org.freedesktop.DBus.Introspectable") } func (a *SecretAgent) GetSecrets( diff --git a/core/internal/server/network/backend_iwd.go b/core/internal/server/network/backend_iwd.go index 536d032f2..a9b14c002 100644 --- a/core/internal/server/network/backend_iwd.go +++ b/core/internal/server/network/backend_iwd.go @@ -101,14 +101,13 @@ func NewIWDBackend() (*IWDBackend, error) { } func (b *IWDBackend) Initialize() error { - conn, err := dbus.ConnectSystemBus() + conn, err := dbus.SystemBus() if err != nil { return fmt.Errorf("failed to connect to system bus: %w", err) } b.conn = conn if err := b.discoverDevices(); err != nil { - conn.Close() return fmt.Errorf("failed to discover iwd devices: %w", err) } @@ -117,7 +116,6 @@ func (b *IWDBackend) Initialize() error { } if err := b.updateState(); err != nil { - conn.Close() return fmt.Errorf("failed to get initial state: %w", err) } @@ -131,10 +129,6 @@ func (b *IWDBackend) Close() { if b.iwdAgent != nil { b.iwdAgent.Close() } - - if b.conn != nil { - b.conn.Close() - } } func (b *IWDBackend) discoverDevices() error { diff --git a/core/internal/server/network/backend_networkd.go b/core/internal/server/network/backend_networkd.go index aa74c0e8f..ca4fdc4b0 100644 --- a/core/internal/server/network/backend_networkd.go +++ b/core/internal/server/network/backend_networkd.go @@ -85,19 +85,17 @@ func NewSystemdNetworkdBackend() (*SystemdNetworkdBackend, error) { } func (b *SystemdNetworkdBackend) Initialize() error { - c, err := dbus.ConnectSystemBus() + c, err := dbus.SystemBus() if err != nil { return fmt.Errorf("connect bus: %w", err) } b.conn = c if err := b.enumerateLinks(); err != nil { - c.Close() return fmt.Errorf("enumerate links: %w", err) } if err := b.updateState(); err != nil { - c.Close() return fmt.Errorf("update initial state: %w", err) } @@ -108,8 +106,13 @@ func (b *SystemdNetworkdBackend) Close() { close(b.stopChan) b.StopMonitoring() - if b.conn != nil { - b.conn.Close() + if b.conn == nil || b.signals == nil { + return + } + b.conn.RemoveSignal(b.signals) + close(b.signals) + for _, rule := range networkdMatchRules { + b.conn.BusObject().Call("org.freedesktop.DBus.RemoveMatch", 0, rule) } } diff --git a/core/internal/server/network/backend_networkd_signals.go b/core/internal/server/network/backend_networkd_signals.go index dc642ed60..0f47ddbf6 100644 --- a/core/internal/server/network/backend_networkd_signals.go +++ b/core/internal/server/network/backend_networkd_signals.go @@ -7,18 +7,18 @@ import ( "github.com/godbus/dbus/v5" ) +var networkdMatchRules = []string{ + "type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged',path_namespace='/org/freedesktop/network1'", + "type='signal',interface='org.freedesktop.network1.Manager'", +} + func (b *SystemdNetworkdBackend) StartMonitoring(onStateChange func()) error { b.onStateChange = onStateChange b.signals = make(chan *dbus.Signal, 64) b.conn.Signal(b.signals) - matchRules := []string{ - "type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged',path_namespace='/org/freedesktop/network1'", - "type='signal',interface='org.freedesktop.network1.Manager'", - } - - for _, rule := range matchRules { + for _, rule := range networkdMatchRules { if err := b.conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule).Err; err != nil { return fmt.Errorf("add match %q: %w", rule, err) } diff --git a/core/internal/server/network/backend_networkmanager_signals.go b/core/internal/server/network/backend_networkmanager_signals.go index eaaebf4ff..11a12bf9c 100644 --- a/core/internal/server/network/backend_networkmanager_signals.go +++ b/core/internal/server/network/backend_networkmanager_signals.go @@ -12,7 +12,7 @@ const ( ) func (b *NetworkManagerBackend) startSignalPump() error { - conn, err := dbus.ConnectSystemBus() + conn, err := dbus.SystemBus() if err != nil { return err } @@ -28,7 +28,6 @@ func (b *NetworkManagerBackend) startSignalPump() error { dbus.WithMatchMember("PropertiesChanged"), ); err != nil { conn.RemoveSignal(signals) - conn.Close() return err } @@ -43,7 +42,6 @@ func (b *NetworkManagerBackend) startSignalPump() error { dbus.WithMatchMember("PropertiesChanged"), ) conn.RemoveSignal(signals) - conn.Close() return err } @@ -63,7 +61,6 @@ func (b *NetworkManagerBackend) startSignalPump() error { dbus.WithMatchMember("NewConnection"), ) conn.RemoveSignal(signals) - conn.Close() return err } @@ -88,7 +85,6 @@ func (b *NetworkManagerBackend) startSignalPump() error { dbus.WithMatchMember("ConnectionRemoved"), ) conn.RemoveSignal(signals) - conn.Close() return err } @@ -98,7 +94,6 @@ func (b *NetworkManagerBackend) startSignalPump() error { dbus.WithMatchMember("DeviceAdded"), ); err != nil { conn.RemoveSignal(signals) - conn.Close() return err } @@ -108,7 +103,6 @@ func (b *NetworkManagerBackend) startSignalPump() error { dbus.WithMatchMember("DeviceRemoved"), ); err != nil { conn.RemoveSignal(signals) - conn.Close() return err } @@ -119,7 +113,6 @@ func (b *NetworkManagerBackend) startSignalPump() error { dbus.WithMatchMember("PropertiesChanged"), ); err != nil { conn.RemoveSignal(signals) - conn.Close() return err } } @@ -131,7 +124,6 @@ func (b *NetworkManagerBackend) startSignalPump() error { dbus.WithMatchMember("PropertiesChanged"), ); err != nil { conn.RemoveSignal(signals) - conn.Close() return err } } @@ -145,7 +137,6 @@ func (b *NetworkManagerBackend) startSignalPump() error { dbus.WithMatchMember("VpnStateChanged"), ); err != nil { conn.RemoveSignal(signals) - conn.Close() return err } @@ -155,7 +146,6 @@ func (b *NetworkManagerBackend) startSignalPump() error { dbus.WithMatchMember("StateChanged"), ); err != nil { conn.RemoveSignal(signals) - conn.Close() return err } @@ -248,7 +238,6 @@ func (b *NetworkManagerBackend) stopSignalPump() { b.sigWG.Wait() - b.dbusConn.Close() } func (b *NetworkManagerBackend) handleDBusSignal(sig *dbus.Signal) { diff --git a/core/internal/server/network/secret_service.go b/core/internal/server/network/secret_service.go index 1a93d2eb2..ae7a085f2 100644 --- a/core/internal/server/network/secret_service.go +++ b/core/internal/server/network/secret_service.go @@ -10,11 +10,12 @@ import ( ) const ( - secretServiceBusName = "org.freedesktop.secrets" - secretServicePath = "/org/freedesktop/secrets" - secretServiceIface = "org.freedesktop.Secret.Service" - secretItemIface = "org.freedesktop.Secret.Item" - secretPromptIface = "org.freedesktop.Secret.Prompt" + secretServiceBusName = "org.freedesktop.secrets" + secretServicePath = "/org/freedesktop/secrets" + secretServiceIface = "org.freedesktop.Secret.Service" + secretServiceSessionIface = "org.freedesktop.Secret.Session" + secretItemIface = "org.freedesktop.Secret.Item" + secretPromptIface = "org.freedesktop.Secret.Prompt" ) type secretServiceSession struct { @@ -24,7 +25,7 @@ type secretServiceSession struct { } func openSecretService() (*secretServiceSession, error) { - c, err := dbus.ConnectSessionBus() + c, err := dbus.SessionBus() if err != nil { return nil, err } @@ -34,11 +35,9 @@ func openSecretService() (*secretServiceSession, error) { var sessionPath dbus.ObjectPath call := svc.Call(secretServiceIface+".OpenSession", 0, "plain", dbus.MakeVariant("")) if call.Err != nil { - c.Close() return nil, call.Err } if err := call.Store(new(dbus.Variant), &sessionPath); err != nil { - c.Close() return nil, err } @@ -177,7 +176,7 @@ func (s *secretServiceSession) lookup(connUuid, settingName, settingKey string) } func (s *secretServiceSession) close() { - s.conn.Close() + s.conn.Object(secretServiceBusName, s.sessionPath).Call(secretServiceSessionIface+".Close", 0) } func (a *SecretAgent) trySecretService( diff --git a/core/internal/server/notifyactions/handlers.go b/core/internal/server/notifyactions/handlers.go new file mode 100644 index 000000000..84ac1a596 --- /dev/null +++ b/core/internal/server/notifyactions/handlers.go @@ -0,0 +1,29 @@ +package notifyactions + +import ( + "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" +) + +func HandleRequest(conn *models.Conn, req models.Request, manager *Manager) { + switch req.Method { + case "notify.watchAction": + handleWatchAction(conn, req, manager) + default: + models.RespondError(conn, req.ID, "unknown method") + } +} + +func handleWatchAction(conn *models.Conn, req models.Request, manager *Manager) { + id, ok := models.Get[float64](req, "id") + if !ok || id <= 0 { + models.RespondError(conn, req.ID, "invalid id parameter") + return + } + path, ok := models.Get[string](req, "path") + if !ok || path == "" { + models.RespondError(conn, req.ID, "invalid path parameter") + return + } + manager.Watch(uint32(id), path) + models.Respond(conn, req.ID, "ok") +} diff --git a/core/internal/server/notifyactions/manager.go b/core/internal/server/notifyactions/manager.go new file mode 100644 index 000000000..289c1313e --- /dev/null +++ b/core/internal/server/notifyactions/manager.go @@ -0,0 +1,117 @@ +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" +) + +type Manager struct { + conn *dbus.Conn + signals chan *dbus.Signal + mu sync.Mutex + watched map[uint32]string +} + +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 + } + m := &Manager{ + conn: conn, + signals: make(chan *dbus.Signal, 32), + watched: make(map[uint32]string), + } + conn.Signal(m.signals) + go m.loop() + return m, nil +} + +func (m *Manager) Watch(id uint32, path string) { + m.mu.Lock() + m.watched[id] = path + m.mu.Unlock() +} + +func (m *Manager) Close() { + m.conn.RemoveSignal(m.signals) + _ = m.conn.RemoveMatchSignal(dbus.WithMatchObjectPath(notifyPath), dbus.WithMatchInterface(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) + } + } +} + +func (m *Manager) take(sig *dbus.Signal) (string, bool) { + if len(sig.Body) < 1 { + return "", false + } + id, ok := sig.Body[0].(uint32) + if !ok { + return "", false + } + m.mu.Lock() + defer m.mu.Unlock() + path, ok := m.watched[id] + if !ok { + return "", false + } + delete(m.watched, id) + return path, 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 + } + path, ok := m.take(sig) + if !ok { + return + } + if action == "folder" { + path = filepath.Dir(path) + } + openPath(path) +} + +func openPath(path string) { + cmd := exec.Command("xdg-open", path) + cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} + if err := cmd.Start(); err != nil { + log.Warnf("notifyactions: xdg-open %s: %v", path, err) + return + } + go func() { _ = cmd.Wait() }() +} diff --git a/core/internal/server/router.go b/core/internal/server/router.go index 97af4f0d7..bd2e750d1 100644 --- a/core/internal/server/router.go +++ b/core/internal/server/router.go @@ -18,6 +18,7 @@ import ( "github.com/AvengeMedia/DankMaterialShell/core/internal/server/mime" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/network" + "github.com/AvengeMedia/DankMaterialShell/core/internal/server/notifyactions" serverPlugins "github.com/AvengeMedia/DankMaterialShell/core/internal/server/plugins" serverRegistries "github.com/AvengeMedia/DankMaterialShell/core/internal/server/registries" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/sysupdate" @@ -209,6 +210,15 @@ func RouteRequest(conn *models.Conn, req models.Request) { return } + if strings.HasPrefix(req.Method, "notify.") { + if notifyActionsManager == nil { + models.RespondError(conn, req.ID, "notification action manager not initialized") + return + } + notifyactions.HandleRequest(conn, req, notifyActionsManager) + return + } + if strings.HasPrefix(req.Method, "sysupdate.") { if sysUpdateManager == nil { models.RespondError(conn, req.ID, "sysupdate manager not initialized") diff --git a/core/internal/server/runtime.go b/core/internal/server/runtime.go new file mode 100644 index 000000000..c6f5cfc2e --- /dev/null +++ b/core/internal/server/runtime.go @@ -0,0 +1,38 @@ +package server + +import ( + "net/http" + _ "net/http/pprof" + "os" + "runtime" + "runtime/debug" + + "github.com/AvengeMedia/DankMaterialShell/core/internal/log" +) + +const ( + serverMaxProcs = 4 + serverGCPercent = 50 +) + +func tuneRuntime() { + if os.Getenv("GOMAXPROCS") == "" { + runtime.GOMAXPROCS(min(runtime.NumCPU(), serverMaxProcs)) + } + if os.Getenv("GOGC") == "" { + debug.SetGCPercent(serverGCPercent) + } +} + +func startPprof() { + addr := os.Getenv("DMS_PPROF") + if addr == "" { + return + } + go func() { + log.Infof("pprof listening on http://%s/debug/pprof/", addr) + if err := http.ListenAndServe(addr, nil); err != nil { + log.Warnf("pprof server: %v", err) + } + }() +} diff --git a/core/internal/server/server.go b/core/internal/server/server.go index 981a2b609..9cc808e17 100644 --- a/core/internal/server/server.go +++ b/core/internal/server/server.go @@ -24,6 +24,7 @@ import ( "github.com/AvengeMedia/DankMaterialShell/core/internal/server/loginctl" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/network" + "github.com/AvengeMedia/DankMaterialShell/core/internal/server/notifyactions" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/sysupdate" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/tailscale" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/thememode" @@ -75,6 +76,7 @@ var wallpaperManager *wallpaper.Manager var trayRecoveryManager *trayrecovery.Manager var locationManager *location.Manager var sysUpdateManager *sysupdate.Manager +var notifyActionsManager *notifyactions.Manager var geoClientInstance geolocation.Client const dbusClientID = "dms-dbus-client" @@ -353,6 +355,16 @@ func InitializeLocationManager(geoClient geolocation.Client) error { return nil } +func InitializeNotifyActionsManager() error { + manager, err := notifyactions.NewManager() + if err != nil { + return err + } + notifyActionsManager = manager + log.Info("Notification action manager initialized") + return nil +} + func InitializeSysUpdateManager() error { manager, err := sysupdate.NewManager() if err != nil { @@ -1259,6 +1271,9 @@ func cleanupManagers() { if dbusManager != nil { dbusManager.Close() } + if notifyActionsManager != nil { + notifyActionsManager.Close() + } if themeModeManager != nil { themeModeManager.Close() } @@ -1319,6 +1334,9 @@ func (s *Server) Serve(printDocs bool) error { defer s.ipc.Close() defer cleanupManagers() + tuneRuntime() + startPprof() + // Tailscale manager always starts — reconnects internally via WatchIPNBus. // The capability is only advertised once tailscaled is reachable; the // callback wakes capability subscribers so QML clients see it transition. @@ -1473,6 +1491,8 @@ func (s *Server) Serve(printDocs bool) error { log.Info(" clipboard.getConfig - Get clipboard configuration") log.Info(" clipboard.setConfig - Set configuration (params: maxHistory?, maxEntrySize?, autoClearDays?, clearAtStartup?)") log.Info(" clipboard.subscribe - Subscribe to clipboard state changes (streaming)") + log.Info("Notify:") + log.Info(" notify.watchAction - Open a file when a notification action fires (params: id, path)") log.Info("Location:") log.Info(" location.getState - Get current location state") log.Info(" location.subscribe - Subscribe to location changes (streaming)") @@ -1696,6 +1716,12 @@ func (s *Server) Serve(printDocs bool) error { } }() + go func() { + if err := InitializeNotifyActionsManager(); err != nil { + log.Warnf("Notification action manager unavailable: %v", err) + } + }() + if err := InitializeSysUpdateManager(); err != nil { log.Warnf("Sysupdate manager unavailable: %v", err) } diff --git a/core/internal/server/trayrecovery/manager.go b/core/internal/server/trayrecovery/manager.go index bb3859515..4a9c1e352 100644 --- a/core/internal/server/trayrecovery/manager.go +++ b/core/internal/server/trayrecovery/manager.go @@ -19,7 +19,7 @@ type Manager struct { } func NewManager() (*Manager, error) { - conn, err := dbus.ConnectSessionBus() + conn, err := dbus.SessionBus() if err != nil { return nil, fmt.Errorf("failed to connect to session bus: %w", err) } @@ -87,8 +87,5 @@ func (m *Manager) Close() { close(m.stopChan) } m.wg.Wait() - if m.conn != nil { - m.conn.Close() - } log.Info("TrayRecovery manager closed") } diff --git a/core/internal/server/wayland/manager.go b/core/internal/server/wayland/manager.go index d99b87028..166ff7e04 100644 --- a/core/internal/server/wayland/manager.go +++ b/core/internal/server/wayland/manager.go @@ -126,15 +126,15 @@ func (m *Manager) anyOutputReady() bool { return anyReady } +const prepareForSleepMatchRule = "type='signal',interface='org.freedesktop.login1.Manager',member='PrepareForSleep',path='/org/freedesktop/login1'" + func (m *Manager) setupDBusMonitor() error { - conn, err := dbus.ConnectSystemBus() + conn, err := dbus.SystemBus() if err != nil { return fmt.Errorf("system bus: %w", err) } - matchRule := "type='signal',interface='org.freedesktop.login1.Manager',member='PrepareForSleep',path='/org/freedesktop/login1'" - if err := conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, matchRule).Err; err != nil { - conn.Close() + if err := conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, prepareForSleepMatchRule).Err; err != nil { return fmt.Errorf("add match: %w", err) } @@ -1281,6 +1281,6 @@ func (m *Manager) Close() { if m.dbusConn != nil { m.dbusConn.RemoveSignal(m.dbusSignal) - m.dbusConn.Close() + m.dbusConn.BusObject().Call("org.freedesktop.DBus.RemoveMatch", 0, prepareForSleepMatchRule) } }