From 10e8086874fdddfa318d30e08caa8f03202e085b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=A1ssio=20Guerreiro=20Antunes=20Virg=C3=ADnio?= Date: Fri, 25 Sep 2026 10:36:01 -0300 Subject: [PATCH] trayrecovery: recover SNI items orphaned at first boot (#3570) Autostart apps (e.g. KeePassXC) that call RegisterStatusNotifierItem before the watcher owns its name fail silently and never re-register. Until now the recovery scan only ran on suspend/resume or watcher owner change, so first-boot orphans were never rescued. Schedule a one-time delayed scan when the watcher first appears. recoverTrayItems() already deduplicates via registeredConnIDs, so this cannot double-register apps that registered normally. --- core/internal/server/trayrecovery/manager.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/core/internal/server/trayrecovery/manager.go b/core/internal/server/trayrecovery/manager.go index 830bc0f3e..9f383cdba 100644 --- a/core/internal/server/trayrecovery/manager.go +++ b/core/internal/server/trayrecovery/manager.go @@ -10,7 +10,10 @@ import ( "github.com/godbus/dbus/v5" ) -const resumeDelay = 3 * time.Second +const ( + resumeDelay = 3 * time.Second + firstBootDelay = 15 * time.Second +) type Manager struct { conn *dbus.Conn @@ -35,7 +38,7 @@ func NewManager() (*Manager, error) { // 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() + go m.scheduleRecovery(resumeDelay) } return m, nil @@ -64,7 +67,7 @@ func (m *Manager) WatchLoginctl(lm *loginctl.Manager) { } if wasSleeping { wasSleeping = false - go m.scheduleRecovery() + go m.scheduleRecovery(resumeDelay) } } } @@ -107,18 +110,23 @@ func (m *Manager) WatchWatcherOwner() { } if !hadOwner { hadOwner = true + // First boot: autostart apps (e.g. KeePassXC) may have tried + // to register their SNI item before the watcher existed and + // never retry. A one-time delayed scan picks those up. + log.Info("TrayRecoveryService: StatusNotifierWatcher appeared for the first time, scheduling boot scan") + go m.scheduleRecovery(firstBootDelay) continue } log.Info("TrayRecoveryService: StatusNotifierWatcher changed hands, scheduling rescan") - go m.scheduleRecovery() + go m.scheduleRecovery(resumeDelay) } } }) } -func (m *Manager) scheduleRecovery() { +func (m *Manager) scheduleRecovery(delay time.Duration) { select { - case <-time.After(resumeDelay): + case <-time.After(delay): m.recoverTrayItems() case <-m.stopChan: }