perf(dms): cut daemon idle RAM, threads, and goroutines. Add DMS_PPROF

for performance profiling
This commit is contained in:
bbedward
2026-08-21 20:24:36 -04:00
parent 523e0232d2
commit 45cd00bed3
35 changed files with 440 additions and 295 deletions
+21 -1
View File
@@ -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)
}
+4 -2
View File
@@ -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)
}
}
+1
View File
@@ -26,6 +26,7 @@ func init() {
}
func main() {
disableMemProfilingUnlessRequested()
clipboard.MaybeServeAndExit()
if os.Geteuid() == 0 && !isReadOnlyCommand(os.Args) {
+1
View File
@@ -22,6 +22,7 @@ func init() {
}
func main() {
disableMemProfilingUnlessRequested()
clipboard.MaybeServeAndExit()
if os.Geteuid() == 0 && !isReadOnlyCommand(os.Args) {
+9
View File
@@ -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 {
+1 -6
View File
@@ -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 {
+41 -34
View File
@@ -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(&notificationID); 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":
+7 -87
View File
@@ -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(&notificationID); 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
}
+9 -5
View File
@@ -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 {
+14 -28
View File
@@ -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 {
+8 -9
View File
@@ -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
+4 -12
View File
@@ -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() {}
@@ -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()
+10 -6
View File
@@ -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
+2
View File
@@ -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"`
@@ -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
}
+14 -13
View File
@@ -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)
}
}
+4 -2
View File
@@ -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]
+13
View File
@@ -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)
+2 -6
View File
@@ -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()
}
}
+1 -6
View File
@@ -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()
}
}
+6 -3
View File
@@ -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) {
@@ -75,7 +75,7 @@ const introspectXML = `
</node>`
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(
+1 -7
View File
@@ -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 {
@@ -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)
}
}
@@ -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)
}
@@ -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) {
@@ -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(
@@ -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")
}
@@ -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() }()
}
+10
View File
@@ -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")
+38
View File
@@ -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)
}
}()
}
+26
View File
@@ -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)
}
+1 -4
View File
@@ -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")
}
+5 -5
View File
@@ -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)
}
}