Fix MenuItem.AddChild not making children visible to the desktop environment#2
Fix MenuItem.AddChild not making children visible to the desktop environment#2alex-poor wants to merge 3 commits intoDeedleFake:masterfrom
Conversation
…child
In MenuItem.AddChild, the dirty property names returned by
child.applyProps were being passed to item.emitPropertiesUpdated,
where item is the parent. This caused emitPropertiesUpdated to look
up the child's property names ("label", "enabled", etc.) in the
parent's props map, where they don't exist, and emit nil values over
D-Bus, triggering a nil pointer dereference during marshaling.
The result: any error from AddChild on a MenuItem propagates a
"runtime error: invalid memory address or nil pointer dereference",
and the desktop environment never receives the new child item's
property notifications.
Menu.AddChild already does this correctly by calling
child.emitPropertiesUpdated(dirty) on the child.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…bmenu When a MenuItem gains its first child or loses its last, setChildren mutates the item's children-display property in memory but never emits a PropertiesUpdated signal for it. As a result, the desktop environment is never told that a regular menu item has been converted into a submenu (or vice-versa), so the children remain invisible. This was previously somewhat masked by the bug fixed in the previous commit, since item.emitPropertiesUpdated was being called on the parent and accidentally re-emitted the parent's id, which some implementations may have used as a hint to refetch. With that bug fixed, the missing children-display emit becomes the only thing preventing newly added children from showing. setChildren now reports the names of any properties it changed, and each caller (MenuItem.AddChild, Remove, appendChild, MoveBefore) emits PropertiesUpdated for the affected node when needed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
….GetLayout MenuItem.SetProps acquired item.m before menu.m, while dbusmenu's GetLayout (called by the desktop environment over D-Bus) acquires menu.m before iterating and RLock'ing each item.m. With concurrent SetProps from one goroutine and GetLayout from another, the two holders deadlock waiting for each other's lock. In practice this never showed up for the existing few status-bar SetProps calls, because they happen during a brief synchronous burst in Tray.update under a higher-level mutex. But any caller that runs SetProps from a separate goroutine while the DE is actively fetching the layout (which it does any time the menu is opened or after a LayoutUpdated signal) hits the deadlock reliably. Reorder SetProps to take menu.m first, then item.m, matching the lock order GetLayout uses. The parent lookup that previously called getParent (which would itself try to RLock menu.m) is inlined since we already hold menu.m at that point. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Pushed a third commit that fixes a related lock-order inversion in `MenuItem.SetProps`. `SetProps` previously locked `item.m` before `menu.m` (line 379 then line 382). Meanwhile `dbusmenu.GetLayout` (the D-Bus method the desktop environment calls to fetch the menu) takes `menu.m` first and then `RLock`s each `item.m` as it walks the tree. With concurrent `SetProps` and `GetLayout`, the two paths deadlock waiting for each other. In practice this hadn't shown up because `Tray.update` in trayscale runs all of its `SetProps` calls in a tight burst from a single goroutine under a higher-level mutex, and the DE rarely tries to `GetLayout` during that burst. The moment a caller invokes `SetProps` from a separate goroutine while the DE is fetching the layout (which it does on every menu open and after every `LayoutUpdated` signal), the deadlock is reliable. The fix swaps the lock order in `SetProps` to match `GetLayout` and inlines the parent lookup that used to call `getParent` (which itself `RLock`s `menu.m` and would deadlock under the new order). |
Allow switching Tailscale profiles directly from the system tray right-click menu, without needing to open the main window. The active profile is shown with a bullet indicator that updates whether the switch is initiated from the tray submenu or from the main window's profile dropdown. Depends on DeedleFake/tray#2, which fixes three bugs in the tray library that were preventing submenu children from rendering and causing a deadlock when SetProps is called concurrently with the desktop environment's GetLayout requests. The temporary replace directive in go.mod should be removed once that PR is merged and deedles.dev/tray is updated to a new pseudo-version. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
Two related bugs in
MenuItem.AddChildcause newly added submenu children to never become visible to the desktop environment.Bug 1:
MenuItem.AddChildcallsemitPropertiesUpdatedon the parentIn menuitem.go:78, the dirty property names returned by `child.applyProps` are passed to `item.emitPropertiesUpdated`, where `item` is the parent, not the child:
```go
dirty, errs := child.applyProps(props)
errs = append(errs, item.menu.updateLayout(item))
errs = append(errs, item.emitPropertiesUpdated(dirty)) // wrong receiver
```
Inside `emitPropertiesUpdated`, this looks up child property names (`"label"`, `"enabled"`, etc.) in the parent's `props` map, where they don't exist:
```go
for change := range props {
updated = append(updated, prop{
Name: change,
Value: item.props[change], // nil for child prop names
})
}
```
The resulting D-Bus emit fails with `"runtime error: invalid memory address or nil pointer dereference"`. `Menu.AddChild` already does this correctly by calling `child.emitPropertiesUpdated(dirty)`.
Bug 2: `setChildren` mutates `children-display` without emitting it
`MenuItem.setChildren` sets `item.props["children-display"] = "submenu"` when the item gains its first child, but never marks this property as dirty or emits a `PropertiesUpdated` signal for it. As a result, the desktop environment is never told that the item has been converted into a submenu, so children added via `MenuItem.AddChild` remain invisible — even after Bug 1 is fixed.
`setChildren` now reports the names of any properties it changed, and each caller (`MenuItem.AddChild`, `Remove`, `appendChild`, `MoveBefore`) emits `PropertiesUpdated` for the affected node when needed.
Test plan
🤖 Generated with Claude Code