mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-08 14:39:09 +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:
+15
-23
@@ -23,19 +23,17 @@ type exitNodeEntry struct {
|
||||
Selected bool
|
||||
}
|
||||
|
||||
// rebuildExitNodes paints one clickable row per exit-node candidate into the
|
||||
// Exit Node submenu. Each row carries the network's NetID and its selected
|
||||
// state from ListNetworks; clicking toggles it via toggleExitNode. The active
|
||||
// node is marked with a "✓ " prefix using a plain Add rather than AddCheckbox
|
||||
// for the same reason as loadProfiles — Wails auto-toggles a checkbox's state
|
||||
// on click before the OnClick handler runs, so the deselect/select round-trip
|
||||
// would briefly show two checked rows. Rebuilds via Clear + Add so the row set
|
||||
// stays in sync; SetMenu on the root menu is required because Wails v3 alpha
|
||||
// menu Update() builds a detached NSMenu on darwin that never replaces the
|
||||
// empty submenu attached at initial setup (same workaround as loadProfiles).
|
||||
// Callers must hold exitNodesRebuildMu so concurrent rebuilds can't race the
|
||||
// submenu's item slice.
|
||||
func (t *Tray) rebuildExitNodes(nodes []exitNodeEntry) {
|
||||
// fillExitNodeSubmenu paints one clickable row per exit-node candidate into
|
||||
// the (freshly built) Exit Node submenu. Each row carries the network's NetID
|
||||
// and its selected state from ListNetworks; clicking toggles it via
|
||||
// toggleExitNode. The active node is marked with a "✓ " prefix using a plain
|
||||
// Add rather than AddCheckbox for the same reason as fillProfileSubmenu —
|
||||
// Wails auto-toggles a checkbox's state on click before the OnClick handler
|
||||
// runs, so the deselect/select round-trip would briefly show two checked rows.
|
||||
// Pure UI: it never calls SetMenu — relayoutMenu owns the single SetMenu that
|
||||
// pushes the whole tree. Callers must hold exitNodesRebuildMu so concurrent
|
||||
// rebuilds can't race the submenu's item slice.
|
||||
func (t *Tray) fillExitNodeSubmenu(nodes []exitNodeEntry) {
|
||||
if t.exitNodeSubmenu == nil {
|
||||
return
|
||||
}
|
||||
@@ -51,9 +49,6 @@ func (t *Tray) rebuildExitNodes(nodes []exitNodeEntry) {
|
||||
t.toggleExitNode(id, selected)
|
||||
})
|
||||
}
|
||||
if t.menu != nil {
|
||||
t.tray.SetMenu(t.menu)
|
||||
}
|
||||
}
|
||||
|
||||
// refreshExitNodes re-fetches the routed-network list from the daemon and
|
||||
@@ -93,14 +88,11 @@ func (t *Tray) refreshExitNodes() {
|
||||
t.exitNodes = nodes
|
||||
t.exitNodesMu.Unlock()
|
||||
|
||||
// Set enablement before rebuildExitNodes' SetMenu so the rebuild reads the
|
||||
// updated state at NSMenuItem construction time (Wails v3 alpha reads
|
||||
// item.disabled at build time, not lazily).
|
||||
if t.exitNodeItem != nil {
|
||||
t.exitNodeItem.SetEnabled(connected && len(nodes) > 0)
|
||||
}
|
||||
// relayoutMenu rebuilds the whole tree (allocating a fresh exitNodeItem) and
|
||||
// repaints the parent's enablement from the cached entries we just stored,
|
||||
// so there is no need to poke the old exitNodeItem here.
|
||||
if changed {
|
||||
t.rebuildExitNodes(nodes)
|
||||
t.relayoutMenu()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user