The item-ordering feature made Host::reorderItems() re-run the full
remove/add path over items_ via std::ranges::for_each(on_remove_/on_add_).
This caused two confirmed bugs:
BUG 1 (iterator invalidation / UAF): on_add_ (Tray::onAdd) calls
Host::checkIgnoreList, which erases from items_ while for_each is still
iterating items_, invalidating iterators/pointers. Triggered by a
non-empty ignore-list matching an item with >=2 items present.
BUG 2 (double add): reorderItems runs while an item's Id is resolved in
proxyReady, i.e. before setReady(). It added the not-yet-ready item
(re-parenting its event_box, pushing into Tray::items_, connecting
signal_show/hide), then setReady() -> itemReady -> onAdd added it again:
GTK 'widget already has a parent' critical, duplicate Item* and
signal handlers that accumulated unbounded.
Fixes:
- reorderItems() now only reorders already-added GTK box children via a
dedicated on_reorder_ callback (Tray::reorderBox), never re-adding or
removing. reorderBox stable-sorts items_ by order_ and repositions
children with gtk_box_reorder_child (honouring reverse-direction).
- Tray::onAdd is idempotent (guards against an already-added item) and
positions the new widget via reorderBox before the ignore-list check.
- signal_show/signal_hide connections are stored per item and
disconnected in Tray::onRemove; onRemove is a no-op for items that were
never added.
Store the retry timeout connection in a Host member and disconnect it in
~Host, so a Host destroyed within the retry window no longer invokes the
timeout callback on freed memory. Cap the retries (MAX_RETRIES) so a
watcher that never reappears no longer spins forever every 200ms; reset
the counter on success and when the watcher name vanishes.
Tray::update() already had logic to hide the module when all child items were passive, but it was only re-run on item add/remove — never on a status change. When the last visible item transitioned to Passive, the item hid itself but the now-empty module remained visible.
Connect Tray to each item's Gtk::EventBox signal_show/signal_hide so update() runs on every visibility transition, and simplify update() to check child->get_visible() directly instead of inspecting the `passive` CSS class. The Tray-level `show-passive-items` read becomes redundant since Item already honours it when deciding its own visibility; remove it along with the now-unused Tray::show_passive_ member.
Fixes: #3721
Load attention and overlay pixmaps from item properties, watch the
corresponding update signals, and prefer attention artwork while an item is in
NeedsAttention state.
When an item only exports an attention movie asset, fall back to loading that
asset as a static pixbuf so the tray still shows the alert state.
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
Only add tray widgets after the SNI proxy has finished initializing and the
item has a valid id/category pair.
This also removes invalid items through the host teardown path, refreshes the
tray when item status changes, and avoids calling DBus methods through a null
proxy during early clicks or scroll events.
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
- Replaced pass-by-value std::string parameters with const std::string&
or std::string_view to prevent SSO overallocations.
- Refactored static mapping functions in UPower to return
std::string_view instead of constructing std::string literals, enabling
perfect cache locality.
- Optimized string concatenation in hot loops (network IPs, inhibitor
lists, sway window marks) by using std::string::append() and
pre-reserving capacity instead of overloaded operator+ which produces
temporary heap instances.
These optimizations reduce high-frequency memory churn and overall heap
fragmentation within the main rendering loops.
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
On the `Passive` value of `Status` tray items would be hidden unless
`show-passive-items` is set to true.
On the `NeedsAttention` value of `Status` tray items will have a
`.needs-attention` CSS class.
It seems that dbusmenu is not ready to display menu immediately and
needs some time to sync data via DBus.
Fixes LIBDBUSMENU-GLIB-CRITICAL: dbusmenu_menuitem_send_about_to_show:
assertion 'DBUSMENU_IS_MENUITEM(mi)' failed.
Also fixes initial render of the menu with layer shell popups support patch.
Set ItemIsMenu to true by default because libappindicator supports
neither ItemIsMenu nor Activate method and compiant SNI implementations
are expected to reset the flag during initial property fetch.
To be revisited if anyone finds the implementation that has Activate
but does not set ItemIsMenu.