diff --git a/core/internal/server/trayrecovery/manager.go b/core/internal/server/trayrecovery/manager.go index 3610f1b8..6053609d 100644 --- a/core/internal/server/trayrecovery/manager.go +++ b/core/internal/server/trayrecovery/manager.go @@ -8,6 +8,7 @@ import ( "github.com/AvengeMedia/DankMaterialShell/core/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/loginctl" "github.com/godbus/dbus/v5" + "golang.org/x/sys/unix" ) const resumeDelay = 3 * time.Second @@ -29,11 +30,14 @@ func NewManager() (*Manager, error) { stopChan: make(chan struct{}), } - // Run a startup scan after a delay — covers the case where the process - // was killed during suspend and restarted by systemd (Type=dbus). - // The fresh process never sees the PrepareForSleep true→false transition, - // so the loginctl watcher alone is not enough. - go m.scheduleRecovery() + // Only run a startup scan when the system has been suspended at least once. + // On a fresh boot CLOCK_BOOTTIME ≈ CLOCK_MONOTONIC (difference ~0). + // After any suspend/resume cycle the difference grows by the time spent + // sleeping. This avoids duplicate registrations on normal boot where apps + // are still starting up and will register their own tray icons shortly. + if timeSuspended() > 5*time.Second { + go m.scheduleRecovery() + } return m, nil } @@ -91,3 +95,21 @@ func (m *Manager) Close() { } log.Info("TrayRecovery manager closed") } + +// timeSuspended returns how long the system has spent in suspend since boot. +// It is the difference between CLOCK_BOOTTIME (includes suspend) and +// CLOCK_MONOTONIC (excludes suspend). +func timeSuspended() time.Duration { + var bt, mt unix.Timespec + if err := unix.ClockGettime(unix.CLOCK_BOOTTIME, &bt); err != nil { + return 0 + } + if err := unix.ClockGettime(unix.CLOCK_MONOTONIC, &mt); err != nil { + return 0 + } + diff := (bt.Sec-mt.Sec)*int64(time.Second) + (bt.Nsec - mt.Nsec) + if diff < 0 { + return 0 + } + return time.Duration(diff) +}