mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-27 14:59:09 +01: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:
@@ -72,7 +72,11 @@ func (t *Tray) applyStatus(st services.Status) {
|
||||
go t.refreshExitNodes()
|
||||
}
|
||||
if daemonVersionChanged && t.daemonVersionItem != nil {
|
||||
t.daemonVersionItem.SetLabel(t.loc.T("tray.menu.daemonVersion", "version", st.DaemonVersion))
|
||||
// The version row lives in the About submenu, which KDE/Plasma caches on
|
||||
// first open and never re-fetches on a plain SetLabel (see relayoutMenu's
|
||||
// doc comment). Drive a full relayout so the new version actually paints.
|
||||
// relayoutMenu repaints the label from the cached lastDaemonVersion.
|
||||
t.relayoutMenu()
|
||||
}
|
||||
if sessionExpiredEnter {
|
||||
t.handleSessionExpired()
|
||||
@@ -155,12 +159,11 @@ func (t *Tray) refreshMenuItemsForStatus(st services.Status, connected bool) {
|
||||
// Refresh the Profiles submenu on every status-text transition: the
|
||||
// daemon does not emit an active-profile event, so the startup race
|
||||
// (UI loads profiles before autoconnect picks the persisted profile)
|
||||
// and a CLI "profile select && up" both surface here. Fired AFTER
|
||||
// all SetHidden/SetEnabled writes on the static menu items above so
|
||||
// loadProfiles' SetMenu rebuild (which clearMenu+processMenu the
|
||||
// entire NSMenu and re-assigns item.impl) cannot race those
|
||||
// writes — the Wails 3 alpha menu API is not goroutine-safe and
|
||||
// reads item.disabled/item.hidden at NSMenuItem construction time.
|
||||
// and a CLI "profile select && up" both surface here. loadProfiles
|
||||
// fetches the rows and drives a full relayoutMenu (serialised by menuMu),
|
||||
// so it cannot race the SetHidden/SetEnabled writes on the static items
|
||||
// above — the Wails 3 alpha menu API is not goroutine-safe and reads
|
||||
// item.disabled/item.hidden at NSMenuItem construction time.
|
||||
go t.loadProfiles()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user