MultipleImageStrategy::update() and handleClick() ran util::command::exec
(a blocking fork+exec+read) on the GTK main thread, so the whole bar froze
for the script's duration on every interval and on every click.
Move the exec into a new IStrategy::fetch() hook that the SleeperThread
worker runs before dp.emit(); update() now only parses the cached output
and draws on the main thread (mirroring how custom.cpp separates exec from
formatting). handleClick() uses forkExec() so clicks fire-and-forget instead
of blocking on the command's output. The entries and single-image paths are
unchanged.
The HiDPI code path builds a cairo surface from the pixbuf via
Gdk::Cairo::create_surface_from_pixbuf(pixbuf, scale, image_.get_window()),
which requires a realized Gtk::Image. During startup an image module can
run its first update() before the widget is realized, so get_window()
returns a null Gdk::Window and that path aborts before anything is
logged. The more image modules are configured, the more likely at least
one updates before realization, which is why >2 images reliably kills
startup.
Guard on get_window(): only take the surface path when a window is
available, otherwise fall back to image_.set(pixbuf) (the pre-HiDPI
behavior). This keeps HiDPI crispness once realized and never crashes at
startup.
Fixes#5051.
Post-0.15.0 review of the 0.15.0..HEAD range surfaced regressions and
bugs. This restores backward compatibility for existing configs/CSS,
fixes confirmed defects, and repairs the scdoc man-page build break on
master. Pango-markup tooltips are intentional and were kept.
Backward-compat restorations:
- AModule: honor legacy numeric Gdk::CursorType cursor values (int overload)
- memory: correct GiB divisor (was ~2.3% low); round bare {} placeholders
- wireplumber: scale max-volume into the linear domain so the cap works again
- idle_inhibitor: gate right/middle-click deactivate & scroll on dynamic-timeouts;
accept both dynamic-timeout(s); widen timeout to double (no fractional truncation)
- custom: keep #custom-<name>.<class> CSS selectors working (classes on box_)
- image: don't wordexp-split a single path; fall back to the literal path
- niri/window: restore hide-when-empty (new show-empty opt-in); escape tooltip
- wlr/taskbar: plain-text tooltip when markup is disabled
Bug fixes:
- tray: fix use-after-free in onAdd; guard the watcher retry timeout
- hyprland: clamp max-windows iterator (OOB); drop duplicate language tooltip block
- niri/window: supply {col}/{max_col} args in the empty branch (fmt::format_error)
- mpris: escape {dynamic}/{player} tooltip; fix dangling player; albumArtist source
- mango: fix use-after-free race (dispatch under callback_mutex_)
- mpd: contain throwing checkErrors in noexcept idle paths (no std::terminate/UAF)
- keyboard_state: always render every lock label, with guarded defaults
- bluetooth: bound GATT ReadValue timeout, opt-in + services-resolved gating,
preserve authoritative Battery1 percentage
- wireplumber: fix WpDevice reference leak / NULL handling
- battery, clock, dwl, wayfire, graph, custom_graph, transform, river: assorted
crash/logic fixes
Man page / build:
- niri-workspaces: fix scdoc "indented by an amount greater than 1"
(workspace-taskbar sub-options were mis-indented; breaks man-page build)
- document new show-empty (niri/window); correct network {txBitrate}/{rxBitrate}
Not compiled locally (no gtkmm on this host); C++ build relies on CI.
Man pages validated with scdoc 1.11.4.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR #4390 enabled millisecond intervals but changed image interval parsing so a missing interval could resolve to 1ms. That creates a hot update loop and can spike CPU and destabilize rendering in drawer/group setups (issue #4835).
Use explicit semantics: missing, null, non-numeric, or <=0 interval is treated as "once" (max sleep), while positive numeric values still support ms precision with a 1ms floor. This keeps the intended feature (sub-second polling when requested) without defaulting to busy looping.
Also align waybar-image(5) docs with runtime behavior.
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
Changed approach per maintainer feedback - instead of caching signal values,
just add isInt() check before accessing config_["signal"].asInt() in refresh()
methods. This is simpler and more minimal.
Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com>
Cache signal value during module construction to avoid accessing JSON config
in signal handler context. This prevents crashes when signal field is missing
or not an integer type.
- Custom module: Cache signal_ value in constructor
- Image module: Cache signal_ value in constructor
- Both modules: Use cached value in refresh() method
Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com>
Previously, the only way to select all the module labels was with the
following kind of selector:
```css
.modules-left > widget > label,
.modules-center > widget > label,
.modules-right > widget > label {
/* ... */
}
```
(and a matching block for the `box` containers).
Now, this can be expressed as
```css
label.module, box.module {
/* ... */
}
```