mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-05 13:09:07 +02:00
[client/ui] Fix tray submenu not updating on KDE/Plasma
The Profiles and Exit Node submenus (and the About version/Update rows) stopped reflecting changes on KDE/Plasma: after the first profile switch the menu froze on its initial snapshot, and "Manage Profiles" — plus the profile rows themselves — stopped responding to clicks entirely. Root cause (confirmed via dbus-monitor): Plasma's StatusNotifierItem host caches a submenu's layout the first time it is opened (GetLayout for that submenu id) and never re-fetches it on a LayoutUpdated(parent=0) signal. The old submenu.Clear()+Add() repaint allocated fresh monotonic item ids each time but reused the same submenu container id, so Plasma kept showing the stale snapshot and, on click, sent the stale ids back — which the rebuilt itemMap no longer knew, silently no-op'ing the click. Fix: route every dynamic tray-menu change through a new relayoutMenu that rebuilds the whole tree (buildMenu + repaint cached state + a single SetMenu), allocating brand-new submenu container ids. Plasma treats those as unseen and re-queries them on next open, fixing both the stale paint and the dead clicks. loadProfiles/refreshExitNodes now cache their rows and drive relayoutMenu; the update row goes through a new onMenuChange hook; the daemon-version row relayouts too. relayoutMenu is serialised by menuMu and the fill*Submenu helpers are pure UI (no fetch, no SetMenu) so it never recurses. The whole-tree SetMenu also subsumes the prior darwin detached-NSMenu workaround.
This commit is contained in:
@@ -32,6 +32,13 @@ type trayUpdater struct {
|
||||
// transitions, so the tray can repaint its icon (the small badge
|
||||
// overlay differs between has-update / no-update).
|
||||
onIconChange func()
|
||||
// onMenuChange drives a full tray relayout (Tray.relayoutMenu) after an
|
||||
// event-driven update-state change. The update row lives in the About
|
||||
// submenu, which KDE/Plasma caches on first open and never re-fetches on a
|
||||
// plain SetLabel/SetHidden — so a newly-available update would never paint
|
||||
// there. relayoutMenu rebuilds the whole tree (fresh submenu ids) and
|
||||
// re-attaches this item from the cached state via attach → refreshMenuItem.
|
||||
onMenuChange func()
|
||||
|
||||
mu sync.Mutex
|
||||
item *application.MenuItem
|
||||
@@ -40,7 +47,7 @@ type trayUpdater struct {
|
||||
progressWindowOpen bool // last installing value we acted on
|
||||
}
|
||||
|
||||
func newTrayUpdater(app *application.App, window *application.WebviewWindow, update *services.Update, notifier *notifications.NotificationService, loc *Localizer, onIconChange func()) *trayUpdater {
|
||||
func newTrayUpdater(app *application.App, window *application.WebviewWindow, update *services.Update, notifier *notifications.NotificationService, loc *Localizer, onIconChange func(), onMenuChange func()) *trayUpdater {
|
||||
u := &trayUpdater{
|
||||
app: app,
|
||||
window: window,
|
||||
@@ -48,6 +55,7 @@ func newTrayUpdater(app *application.App, window *application.WebviewWindow, upd
|
||||
notifier: notifier,
|
||||
loc: loc,
|
||||
onIconChange: onIconChange,
|
||||
onMenuChange: onMenuChange,
|
||||
}
|
||||
app.Event.On(updater.EventStateChanged, u.onStateEvent)
|
||||
// Seed from the cached state so we don't miss an event that fired
|
||||
@@ -142,7 +150,16 @@ func (u *trayUpdater) applyState(st updater.State) {
|
||||
}
|
||||
u.mu.Unlock()
|
||||
|
||||
u.refreshMenuItem(st)
|
||||
// Drive a full relayout rather than mutating u.item in place: on KDE the
|
||||
// About submenu is layout-cached, so a direct SetLabel/SetHidden here would
|
||||
// not paint the newly-available update. relayoutMenu re-attaches the item
|
||||
// from u.state, which re-runs refreshMenuItem. Fall back to the in-place
|
||||
// refresh if no relayout hook was wired (defensive — always set today).
|
||||
if u.onMenuChange != nil {
|
||||
u.onMenuChange()
|
||||
} else {
|
||||
u.refreshMenuItem(st)
|
||||
}
|
||||
if prev.Available != st.Available && u.onIconChange != nil {
|
||||
u.onIconChange()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user