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>
Return the host registration method correctly on duplicate host registration
and emit HostUnregistered instead of HostRegistered when the last host
vanishes.
Also free the corresponding name watch once the tracked host/item disappears so
the watcher does not leak stale watch records.
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>
The popup menu was retrieved from GtkBuilder and stored in menu_, but the builder was unref'd immediately after construction. That left the later popup path operating on a builder-owned GtkMenu whose lifetime was no longer guaranteed, which matches the GTK_IS_WIDGET and GTK_IS_MENU assertions from the regression report.
Take an owned reference to the built menu and release it in AModule teardown so popup menus stay valid without extending the lifetime of the whole builder.
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
Waybar SEGVs in Glib::DispatchNotifier::pipe_io_handler when the MPRIS
module is enabled. The crash is intermittent because it requires a race
between signal emission and object destruction: a playerctl GLib signal
callback (e.g. onPlayerPlay) calls dp.emit(), which writes a pointer to
the Dispatcher into an internal pipe. If the Mpris object is destroyed
before the GLib main loop reads that pipe entry, pipe_io_handler
dereferences a dangling pointer. This typically occurs when a media
player appears or vanishes on D-Bus (browser closing, player quitting)
or during waybar shutdown/config reload.
The root cause is that ~Mpris() calls g_object_unref() on the manager
and player GObjects without first disconnecting the signal handlers that
hold raw `this` pointers. If playerctl holds additional references to
these GObjects, they survive the unref and can still fire signals
targeting the already-destroyed Mpris instance.
Adopt the same cleanup pattern used by the Wireplumber module: call
g_signal_handlers_disconnect_by_data() to sever all signal connections
referencing `this` before releasing the GObjects with g_clear_object().
This guarantees no callbacks can enqueue stale Dispatcher notifications
after teardown begins.
Additionally:
- Clean up old player in onPlayerNameAppeared before replacing it,
fixing a GObject leak and accumulation of dangling signal connections
- Remove duplicate onPlayerStop signal registration (copy-paste bug)
This commit addresses memory churn caused by implicit deep copies during traversal and allocation of complex structures:
- Replaced pass-by-value 'Json::Value' in std::ranges and range-based for loops with 'const auto&' or 'const Json::Value&' in Hyprland modules, preventing large JSON tree duplications on every update.
- Fixed implicit string and pair copies in UPower and CPU Frequency loops by converting 'auto' to 'const auto&' where possible.
- Added 'std::vector::reserve' calls before 'push_back' loops in MPRIS, Niri, and CFFI modules to prevent exponential vector reallocation during initialization.
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
Valgrind Massif profiling revealed that invoking Gtk::IconTheme::rescan_if_needed() inside SNI updateImage() and getIconByName() loops caused considerable memory churn and potential filesystem stat overhead whenever a system tray app pushed a metadata update.
This commit removes the rescan polling from the SNI proxy callback pipeline and the DefaultGtkIconThemeWrapper, restricting icon theme caching to load boundaries.
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
Memory profiling via Valgrind Massif indicated that 10-20% of peak memory allocations within the SNI loop resulted from copying DBus image data payloads via g_memdup2 before modifying them from ARGB to RGBA.
This commit optimizes the pixel conversion by directly allocating the final array via g_malloc and running the ARGB->RGBA transposition in a single pass while copying from the read-only GVariant buffer, entirely eliminating the intermediate g_memdup stage.
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>
Command tests did not assert behavior when exec fails in child processes.
I added deterministic regression coverage that forces execl/execlp failure and
verifies non-zero exit status propagation for both open() and forkExec paths.
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
Child exec failure paths were returning success, which masked command launch
errors from callers.
I switched the child-side failure exits to _exit(127) and added errno-specific
logging so failures propagate with actionable diagnostics.
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
The hyprland IPC fixture was no longer used by the current test setup.
I removed the dead fixture so the test code reflects the actual execution path
and is easier to maintain.
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
Hyprland tests did not explicitly verify descriptor behavior on key failure
paths.
I added focused tests for missing instance signature and connect-failure paths
that assert file descriptor counts stay stable across repeated attempts.
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
Hyprland IPC had fd lifecycle risks on failure/shutdown paths and used a
spin-sleep listener model.
I initialized fd state defensively, tightened connect/close/shutdown handling,
moved to blocking read with newline framing, and added RAII-style fd cleanup in
socket1 reply paths.
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
SafeSignal could queue events forever when worker threads emitted faster than
the main loop could consume, which risks memory growth and stale updates.
I added a queue cap with a drop-oldest policy so growth stays bounded under
burst load, plus a regression test that validates bounded delivery.
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
SleeperThread concurrency paths needed stress coverage around wake/stop races.
I added a subprocess stress test that repeatedly interleaves wake_up() and
stop() and verifies the worker exits cleanly.
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
SleeperThread control flags were shared across threads without consistent
synchronization.
I converted the run/signal flags to atomics and updated wait predicates and
lifecycle transitions to use explicit atomic loads/stores.
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
We needed a regression test for reassignment safety after lifecycle fixes.
I added a subprocess test that reassigns SleeperThread workers and verifies the
process exits normally instead of terminating.
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
Reassigning SleeperThread could replace a joinable std::thread and trigger
std::terminate.
I now stop and join any existing worker before reassignment, then reset control
state before starting the replacement worker.
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
- Fix AudioBackend destructor: properly lock the PA mainloop before
disconnecting the context to prevent race conditions with PA callbacks
- Fix context leak on reconnect: call pa_context_unref() when the old
context is replaced after PA_CONTEXT_FAILED to avoid resource leaks
- Fix PA mainloop killed on reconnect (critical): PA_CONTEXT_TERMINATED
was unconditionally calling quit() on the mainloop, even during
reconnection when the old context fires TERMINATED after the new one
was created. This was killing the new context and preventing successful
reconnection, causing Waybar to appear frozen. The fix only quits
the mainloop when the terminating context is still the active one.
- Fix Wireplumber use-after-free: explicitly disconnect GObject signal
handlers for mixer_api_, def_nodes_api_, and om_ before clearing the
object references in the destructor to prevent callbacks from firing
with a destroyed self pointer.
- Fix GVariant memory leak in Wireplumber::handleScroll: unref the
GVariant created for the set-volume signal after the emit call.
Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com>