The privacy module registers timeout callbacks in the GLib main context.
The GeoClue callback uses sigc::mem_fun() with the Privacy object, but the
connection remains active when the module is destroyed during a bar reload.
If the timeout runs after destruction, it dereferences the freed Privacy
object and crashes Waybar:
#0 waybar::modules::privacy::Privacy::locationTimeout(bool)
#4 g_main_context_iteration()
#5 g_application_run()
Disconnect the GeoClue and visibility timeout connections in the Privacy
destructor so no callback can run after the module is gone.
The unpatched binary crashed after four SIGUSR2 reloads at 1.5-second
intervals. The patched binary survived ten reloads.
Fixes: eeb7bc702e ("Added GeoClue2 privacy item")
Assisted-by: Claude Opus + Cursor
Removed hasScrollConfig boolean variable in Workspaces init function. It
was Or'd with barScroll boolean variable and overriding it when
enable-bar-scroll is declared false in waybar config. Setting any scroll
up or down events caused enable-bar-scroll to be overwritten.
Tested on Arch/Hyprland, removing hasScrollConfig and the or condition
restored expected behavior.
Fixes#5229
Ipc::subscribe() assumes the next message on the event socket is the
reply to the IPC_SUBSCRIBE it just sent.
When a module subscribes more than once (sway/workspaces subscribes to
"workspace" and then to "window"), an event from the first subscription
can arrive before the reply to the second one. The payload check fails
and the thrown exception permanently disables the module for that bar.
This is easy to hit when bars are (re)created on output hotplug, since
sway emits a burst of workspace events at exactly that moment while
moving workspaces to the new output:
[warning] module sway/workspaces: Disabling module "sway/workspaces",
Unable to subscribe ipc event
Fix it by skipping over event messages (type high bit set) until the
subscribe reply is found, re-emitting them on signal_event so none are
lost.
Fixes: #5218
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com>
Cache ALabel tooltip markup and provide it through query-tooltip instead of repeatedly setting GtkWidget's tooltip-markup property. This prevents frequently updating modules from restarting GTK's display-wide tooltip timer.
Keep the active tooltip updated directly so dynamic tooltip contents continue refreshing while the pointer remains stationary.
When min-brightness is set, a scroll step that would cross it was not
clamped — only scrolling while already at or below min was blocked.
With scroll-step: 5 and min-brightness: 1, scrolling from 5% would
pass the guard (5 > 1) and set brightness to 0%.
Fix by computing the post-step value before applying it: if it would
fall below min-brightness, set exactly to min-brightness instead.
Synchronize the cava module man page with the current implementation:
- Fix scdoc table syntax (correct header/body cell prefixes)
- Add missing Waybar-side options: format-icons, vertex_shader, fragment_shader
- Remove options not consumed by the module (data_format, raw_target)
- Clarify option scope: raw-only, GLSL-only, and cava-config-only settings
- Update descriptions to match backend behavior:
- ascii_range is derived from format-icons length
- bar_height is ignored by Waybar
- bar_delimiter is used for raw output
- Polish grammar, tighten structure, and fix example filenames
- Refresh STYLE and FRONTENDS sections to match the actual source
hardware.sh called smoke::stop between umockdev cases, but smoke::stop runs
`swaymsg exit` and tears down the shared compositor -- so only the first case
(backlight) had a display and the rest failed with "cannot open display".
Kill only waybar between cases; leave compositor teardown to the EXIT trap.
The state tier started actually running mpd in 74f0d33, which exposed a
deadlock in teardown: state.sh launches `mpd --no-daemon &` and never kills
it during the tier, and its cleanup() called smoke::stop *before* killing mpd.
smoke::stop ended in a bare `wait`, which reaps *every* background job of the
shell -- including the still-running mpd -- so it blocked until GitHub's 6h
job timeout. continue-on-error doesn't help: it catches failures, not hangs.
- lib.sh: smoke::stop now waits only on the PIDs it owns (waybar, compositor),
so an unrelated daemon left running by a tier can't deadlock teardown.
- state.sh: cleanup() tears down mpd/pulseaudio before smoke::stop.
- smoke.yml: add timeout-minutes: 20 so a future hang fails fast instead of
burning the default 6h runner budget.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
onEvent() runs on the Hyprland IPC listener thread and mutated the label's
style context (add/remove class) directly, racing the GTK main thread's
drawing and corrupting the heap (double free / corrupted double-linked list).
Follow the Submap pattern: onEvent only stores the layout under the mutex and
emits the dispatcher; update() swaps the CSS class on the main thread, tracking
the previously applied class.
Fixes#4665
tooltip-format-enumerate-connected[-battery] only received the device_* args,
so {status}, {num_connections} and {controller_*} threw fmt "argument not
found". Pass those args to the enumerate fmt::format as well.
Fixes#4384
loadConfig() called exit(EXIT_FAILURE) when the cava config failed to load or
no input source was available, killing the whole bar. Throw std::runtime_error
instead: the factory/bar catch it at construction and disable only the cava
module. The read_thread_ also calls loadConfig() at runtime, so contain the
throw there too, logging instead of terminating.
Fixes#4456
config_["hide-vacant"].asBool() was called from the river status listeners.
When the option is given as a string ("true") jsoncpp's asBool() throws
"Value is not convertible to bool", and that exception unwinding through
libwayland's C dispatch aborts the process. Parse it once in the constructor
into a bool member (accepting the string form) and read the cached value.
Fixes#4078
The state tier started actually running mpd in 74f0d33, which exposed a
deadlock in teardown: state.sh launches `mpd --no-daemon &` and never kills
it during the tier, and its cleanup() called smoke::stop *before* killing mpd.
smoke::stop ended in a bare `wait`, which reaps *every* background job of the
shell -- including the still-running mpd -- so it blocked until GitHub's 6h
job timeout. continue-on-error doesn't help: it catches failures, not hangs.
- lib.sh: smoke::stop now waits only on the PIDs it owns (waybar, compositor),
so an unrelated daemon left running by a tier can't deadlock teardown.
- state.sh: cleanup() tears down mpd/pulseaudio before smoke::stop.
- smoke.yml: add timeout-minutes: 20 so a future hang fails fast instead of
burning the default 6h runner budget.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The first run went green but the best-effort tiers silently didn't exercise
anything:
- state.sh: the mpd `audio_output { ... }` block was on one line, which mpd
rejects ("Unknown tokens after '{'"), so mpd never started and the mpd
sub-tier (the #5183 empty-queue repro) skipped. Split the block across lines.
Also move teardown into a cleanup() with `|| true` so a dead-pid kill under
`set -e` no longer makes the step exit 1.
- hardware.sh: umockdev-run's LD_PRELOAD lands ahead of the linked-in ASan
runtime, so ASan aborted before main() ("ASan runtime does not come first")
and waybar never started -- the backlight/slider (#5179) path wasn't tested.
Set verify_asan_link_order=0 so the instrumented binary runs under the preload.
- leakcheck.sh: abort_on_error=1 turned LSan's exit report into a core dump;
force abort_on_error=0 for this report-only tier.
- lib.sh: assert_clean now also flags "ASan runtime does not come first" so a
future preload/link-order regression fails loudly instead of passing.
The smoke test only exercised steady-state rendering and killed waybar with
SIGTERM, so whole crash classes were invisible: exit-time use-after-free
(#5182), module state transitions (#5183) and hardware-backed modules (#5179).
Add tiers that hit those paths under ASan (+ _GLIBCXX_ASSERTIONS):
- lib.sh: assert_clean_exit (SIGINT teardown -> checks segfault/abort + ASan
report emitted during destruction), output hotplug helpers, signal/reload,
WAYBAR_WRAP hook, opt-in leak detection.
- lifecycle.sh (gating): clean exit, runtime output hotplug (Bar/module
destroy), toggle/reload churn, fast-interval teardown race (#5182 class).
- fuzz.sh (gating): pathological custom-backend output (empty, nonzero exit,
invalid JSON, huge, non-UTF8, empty format).
- coverage.sh (gating): every Factory module must be classified for smoke
coverage; a new unlisted module fails the job (#5179 slipped through).
- modules.sh: also render the whole matrix inside a group; SIGINT teardown per
module instead of SIGTERM.
- state.sh (best-effort): real mpd driven through an empty queue (#5183) +
stop/clear; pulseaudio + slider on a null sink.
- hardware.sh (best-effort): backlight, backlight/slider (#5179) and battery
via umockdev.
- leakcheck.sh (report-only): clean-exit run under LSan.
Members are destroyed in reverse declaration order, so modules_all_ (and
the modules it owns) are gone before the GtkWindow. Tearing down a mapped
window emits `unmap`, whose handler runs toggleSuspend() over the already
freed modules. Disconnect the map/unmap handlers in ~Bar first.
Fixes#5182
mpd_run_current_song() returns NULL when there is no current song (e.g.
after `mpc clear`), leaving song_ null. setLabel() always evaluates the
fmt::format() tag arguments -- even for format-stopped -- so getTag() and
getFilename() would call mpd_song_get_tag()/mpd_song_get_uri() on a null
song and segfault. Guard both against a null song_.
Fixes#5183
The un-ignore branch in hide_if_ignored() computed is_was_ignored
after the enclosing condition already required ignored_ to be false,
so it never ran, and a task whose app_id/title changed away from an
ignore-list entry stayed hidden forever.
Reset ignored_ and show the button again, subject to the same
all-outputs/output-membership check used everywhere else.
hide_if_duplicate() unconditionally re-ran handle_output_enter() with
the bar's own wl_output for every non-squashed task, so any app_id or
title event made the task visible on all bars and "all-outputs": false
was effectively ignored. The un-squash path in handle_closed() showed
the replacement task unconditionally, with the same effect.
Track whether the toplevel is actually on the bar's output from the
protocol's output_enter/output_leave events, split the button
show/hide logic out of the protocol handlers, and gate every synthetic
re-show on all-outputs or the tracked output membership.
Fixes#5178
The network module only populates the interface address from netlink
events (RTM_NEWADDR) or an explicit address dump. The interval timer
re-queries WiFi and bandwidth but never re-fetches the address, so the
module relies entirely on receiving the RTM_NEWADDR event.
Netlink multicast delivery is reliable unless the socket receive buffer
overflows, in which case the kernel drops notifications and reports
ENOBUFS. During a burst of link/address/route changes -- e.g. a router
reboot or a PPPoE redial -- this can drop the RTM_NEWADDR carrying the
interface's new IP (after the old one was removed by RTM_DELADDR). With
no overrun handling and no periodic resync, the address field stays
blank until Waybar is restarted (which re-dumps addresses).
Handle the overrun: when nl_recvmsgs_default reports ENOBUFS/NLE_NOMEM,
request a fresh link/address (and route, when auto-detecting) dump to
resynchronise, instead of silently continuing with lost state. Also
enlarge the event socket receive buffer to make overruns less likely in
the first place. The fix stays within the event thread, so it adds no
new locking or cross-thread socket access.
Fixes#5122.
The backlight udev worker thread called enumerate_devices() on every
epoll_wait timeout, i.e. once per polling interval. enumerate_devices()
runs udev_enumerate_scan_devices(), which walks the entire
/sys/class/backlight and /sys/class/leds trees and opens/closes the
sysfs root and every device path. With no `interval` configured the
module polls on its default cadence, so this full re-scan ran
continuously even when brightness never changed, flooding the
filesystem (observed via fatrace as constant open/close of `/`).
Raising `interval` only lowered the cadence, which is why the reporter's
`interval: 10` workaround reduced the flood.
The full re-enumeration is redundant: the udev monitor already delivers
change/add/remove events for the backlight and leds subsystems. On the
timeout path, re-read only the sysfs attributes of the devices already
tracked (via udev_device_new_from_subsystem_sysname) instead of
re-scanning the whole tree. This keeps periodic refresh working for
firmware backlights such as acpi_video that may not emit udev change
events, while eliminating the tree-wide scan. Device discovery of
new/removed devices continues through the udev monitor.
Fixes#5020.
Installing the bzip2 package does not ship a pkg-config file, so freetype2.pc
(a transitive requirement of gtkmm-3.0) still failed with 'Package bzip2 ...
not found'. bzip2 lives in the FreeBSD base system; write a minimal bzip2.pc
before meson setup so pkg-config can resolve it.
The FreeBSD build broke on 'Package bzip2, required by freetype2, not found':
freetype2.pc lists bzip2 as a (private) requirement, but no bzip2.pc was
installed, so pkg-config could not generate cflags for gtkmm-3.0. Add bzip2
to the package list.
The GtkBuilder created for menu construction was never unref'd on any
path (success or throw), leaking one builder per graph module with a
menu. Unref on each throw and at the end, and take an explicit ref on
menu_ so it survives dropping the builder (mirrors ALabel).
The per-menu-action string duplicated with g_strdup was never freed,
leaking one string per action on every menu build and reload. Use
g_signal_connect_data with (GClosureNotify)g_free so the copy is freed
when the closure is destroyed.
With a sink-mapping configured, sinkInfoCb could report the wrong sink's
volume depending on the order in which PulseAudio enumerated sinks during a
pa_context_get_sink_info_list sweep.
The mapping override was keyed on the mutable current_sink_name_ and ran
before the 'pick a running sink' fallback, which also mutated
current_sink_name_. If the default sink was running while the mapped target
was suspended, the fallback could reassign the selection to the default sink
after the mapping had already matched, so the reported sink depended on
enumeration order (and each sweep wrote the state twice, causing a flicker).
Resolve the target up front: key the mapping on the stable default_sink_name
and, when a mapping is in effect, treat the mapped target sink as the sole
definitive selection - every other sink is ignored and the running-sink
fallback is skipped. The default-sink + running-fallback behavior is
unchanged when no mapping applies. Verified in isolation across all sink
enumeration orders.
Every pa_operation* returned by the PulseAudio context introspection,
subscribe and volume/mute calls was discarded without pa_operation_unref,
leaking one operation object per call. Over a long session the periodic
subscription events accumulate an unbounded number of these handles.
Capture each returned handle and unref it (guarded against NULL) at every
discard site. Callback behavior is unchanged; these calls already run under
the threaded-mainloop lock, where unref is safe.
Turn the smoke test into a real runtime safety net. Waybar is now built with
AddressSanitizer and exercised end to end in a headless compositor:
- Tier 0: run everything under ASan; fail on ASan reports and Gtk/GLib
criticals in the log (lib.sh assert_clean)
- Tier 1: per-module render matrix (modules.sh) — each headless-safe module
rendered in isolation
- Tier 2: pointer interaction (interact.sh) — inject clicks via sway, assert
on-click side effect and format-alt toggle; AT-SPI assertions (a11y.sh/.py)
check module labels semantically instead of by pixels
- Tier 3: layout matrix (positions.sh) — top/bottom/left + HiDPI scale 2;
second compositor run under labwc
Shared helpers extracted to lib.sh. Adds a workflow_dispatch `bless` input to
regenerate the golden reference in one click. Trigger push only on master to
avoid duplicate PR runs. AT-SPI and labwc steps are continue-on-error.
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.
g_variant_lookup with the "b" format writes a gboolean (gint, 4 bytes),
but muted_ and source_muted_ are C++ bool members (1 byte). Passing their
addresses caused a 3-byte out-of-bounds write past the member (undefined
behavior). Read into a gboolean temporary and assign back to the bool,
preserving the prior value when "mute" is absent.
A malformed user format or tooltip-format (unknown {placeholder}) made
fmt::format throw fmt::format_error out of update(). Wrap the label and
tooltip format calls in try/catch that warn once and fall back to a safe
label / skip the tooltip instead of taking the module down.
Only the label vformat was wrapped in try/catch. An unsupported specifier
(e.g. %-I / %OI) in tooltip-format or the calendar format still threw out
of update() every tick via the calendar/tooltip vformat calls. Wrap the
tooltip-building section in try/catch that warns once and skips the
tooltip for that tick instead of letting the exception escape update().
When playerctl_player_new_from_name() fails for a candidate player, the
loop continued without clearing the GError. The stale non-NULL error then
leaked into the next GLib call (GLib-CRITICAL assertion) and made the
post-loop 'if (error) goto errorexit' fire even when a valid playing
player had been selected, blanking the whole module. Clear the error at
the discard point with g_clear_error().
/sys/class/net/<if>/speed reports -1 with no carrier. Reading it into a
uint32_t wrapped to 4294967295 (without setting failbit), so {linkSpeed}
showed an absurd value. Read into int64_t, check fail(), and treat negative
or failed reads as 0.
setTooltipMarkup uses set_tooltip_markup without escaping. Raw window titles
routinely contain &, < and >, which break Pango markup parsing and the
tooltip. Escape the title with Glib::Markup::escape_text before passing it.
openDevice() throws without closing the fd if libevdev_new_from_fd fails.
In both update() and tryAddDevice() the outer catch only logged, so
closeFile(fd) was never reached and a descriptor leaked on every failing
tick. Guard openDevice with a try/catch that closes the fd before rethrowing.
The parser assumed each entry was '<dimension> <comparator> <value>'.
An entry with no space caused str.substr((size_t)-1) to throw out_of_range,
and a non-integer value made std::stoi throw invalid_argument, failing the
whole bar on that output. Validate spaces with find()!=npos and wrap stoi in
try/catch; log a warning and skip malformed entries instead of throwing.
With reveal-delay set, handleMouseEnter arms a Glib::signal_timeout that
captures 'this'. sigc::connection's destructor does not remove the GLib
source, so a Group destroyed with a pending reveal timer would fire the
timeout on freed memory. Add a destructor that disconnects reveal_timeout_.
The IPC event thread had no reconnect: on POLLHUP/POLLERR/POLLNVAL or
read()==0/error it broke out of the loop and the thread exited
permanently, freezing every mango module with stale content until
Waybar was restarted. Wrap the connect + poll/read loop in a reconnect
loop with a bounded 2s backoff, re-establishing the socket and resuming
on disconnect, modeled on the niri backend.
Add an atomic running_ flag so the worker exits cleanly on teardown;
the destructor now sets it false before closing the socket so the
worker breaks out and joins, and leaves the final close to the
destructor to avoid a double close.
Remove leftover unconditional assignment that clobbered the value
computed from rxkb_layout_get_brief() with short_name, which made
short_description always equal short_name and defeated
format-<shortDescription> / {shortDescription}.
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.
isLuaProtocol() probed the protocol by sending a real
"dispatch workspace __waybar_probe__". On Hyprland < 0.54 "workspace"
is a valid dispatcher, so the probe actually switched the user to a junk
workspace named __waybar_probe__ on the first workspace click/scroll.
Detect the protocol with the read-only "version" IPC query instead:
parse the numeric "version" field (falling back to the always-present
"tag" field) and treat Hyprland >= 0.54 as Lua. This has no side
effects. On any parse/query failure we log and fall back to the legacy
protocol, preserving prior behavior for older versions.
The debounce timer added for flicker prevention was armed from the IPC
listener thread via Glib::signal_timeout().connect(), while its timeout
lambda and the m_updatePending flag ran on the GTK main thread — an
unsynchronized cross-thread data race on GLib timer/main-loop state.
Additionally ~Workspaces() never disconnected the timer, so a pending
timeout could fire on a freed 'this' (use-after-free).
Restore the pre-refactor threading model: onEvent now only mutates state
under m_mutex on the IPC thread and calls dp.emit() (Glib::Dispatcher is
thread-safe). The debounce timer is owned entirely by the main-thread
update() path, which arms/re-arms it on each dispatch and coalesces
bursts into a single refresh. ~Workspaces() disconnects the timer
(guarded) so none outlives the object. Debounce behavior is preserved.
The destructor unconditionally unref'd current_modem, manager and
connection, but the constructor can leave them NULL or already-unref'd:
- On the mm_manager_new_sync failure path the ctor unref'd connection
without nulling it, so the dtor unref'd it a second time -> double-free.
- On the g_bus_get_sync failure path all three stay NULL, and in the
common no-WWAN-hardware case current_modem is NULL, so the dtor ran
g_object_unref(NULL) -> G_IS_OBJECT assertion criticals.
Use g_clear_object() in the failing ctor path (unref + null) and in the
destructor (NULL-safe unref + null). Teardown is now safe for every ctor
outcome (bus fail, MM fail, no modem, normal), and a normal run still
unrefs each owned ref exactly once.
IPC::send() wrapped the socket fd in a util::ScopedFd, which closes the
fd in its destructor. The input stream was created with close_fd=true,
so the stream also closed the same fd, resulting in a double-close. In
multithreaded Waybar another thread can open a new fd with the same
number between the two close() calls, which the second close() then
wrongly closes. Pass close_fd=false so ScopedFd is the sole owner and
the fd is closed exactly once. The streams are declared after socketfd,
so they flush and destruct while the fd is still open, then ScopedFd
closes it.
Bring src/modules/custom_graph.cpp in line with the hardened custom.cpp:
- continuousWorker: on the restart path, an open() failure threw
std::runtime_error out of the SleeperThread lambda, which escaped the
thread and called std::terminate, killing all of Waybar. Log the error
and stop the worker gracefully instead of throwing.
- parseOutputJson: validate/make_valid the text/alt/tooltip JSON string
fields before they reach fmt markup / set_tooltip_markup. Invalid UTF-8
from a script otherwise aborts the bar in g_utf8_* (parseOutputRaw
already validated the same way).
- refresh: wrap the SIGRTMIN-based signal check in #ifdef SIGRTMIN so the
module builds on platforms without SIGRTMIN (e.g. some BSDs).
Fixes a std::terminate crash on continuous-exec restart failure, an
invalid-UTF-8 bar abort via JSON output, and a build break on platforms
lacking SIGRTMIN.
toggleSuspend dynamic_cast<AModule*>-ed the children of the left/center/right
Gtk::Box. But modules are packed via AModule::operator Gtk::Widget&(), which
returns the member event_box_, so every box child is a Gtk::EventBox and the
cast is always null -- suspend()/resume() never ran, making disable-on-sleep a
silent no-op. Iterate modules_all_ (the real module pointers) instead.
Fixes disable-on-sleep DPMS suspend/resume never firing.
handleUserEvent ran the configured command through fmt::format(fmt::runtime(...))
to substitute {x}/{y}. Commands containing literal braces that aren't {x}/{y}
(e.g. `echo ${HOME}`, `awk '{print $1}'`, brace expansions) made libfmt throw
fmt::format_error. Uncaught inside a GTK signal handler this aborts the whole bar.
Only format when {x}/{y} is present and fall back to the raw command on failure.
Fixes bar abort/std::terminate on on-click/on-scroll commands containing braces.
These workflows triggered on both push and pull_request with no branch
filter, so every push to a PR branch ran each workflow twice. Restrict the
push trigger to master; pull_request already covers PR branches.
Addresses review on #5168:
- Generational aliasing (blocking): setupConnection()/onReconnectTimeout()
rebuild wp_core_/om_/pending_plugins_ in place on the same self, but the
async load/activate callbacks carried no generation, and isModuleAlive()
only proves self still exists. If PipeWire dropped again while a previous
connection's async chain was still in flight, a stale completion would run
against the rebuilt connection (a stray --pending_plugins_, an out-of-order
install_object_manager), re-creating #2882's stale/blank state. Each async
call now carries an AsyncCall{self, generation}; connection_generation_ is
bumped in setupConnection(), and every callback drops out when its
generation no longer matches (checked after isModuleAlive short-circuits).
- Duplicate scroll handlers: onMixerApiLoaded re-runs on every reconnect and
connected a new scroll handler each time (dead but accumulating). Moved the
one-time wiring to the constructor; handleScroll no-ops while mixer_api_ is
null, so wiring it before the first connect is safe.
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.
2190871a (perf(label): skip redundant markup updates) caches the last
label/tooltip markup as Glib::ustring and skips set_markup() when the
new markup compares equal. Glib::ustring::operator== goes through
g_utf8_collate(), and under the UTF-8 locale GTK sets at startup,
Unicode private-use codepoints carry no collation weight. All nerd-font
icons live in the PUA, so two labels that differ only in their icon
glyph collate as equal and the visual update is silently dropped.
idle_inhibitor is the visible victim (#5169): clicking toggles the
state class (highlight changes) but the {icon} glyph never switches
between the activated/deactivated icons, while plain-text icons like
"YES"/"NO" work. Any ALabel module whose consecutive updates differ
only by a PUA glyph is affected. The module only started routing
through this cache when db4941ef migrated it onto the shared
setLabelMarkup()/setTooltipMarkup() helpers, which is why it broke in
the latest batch of refactors.
Store the cache as raw UTF-8 bytes (std::string via ustring::raw())
and compare those instead, so the skip only triggers on byte-identical
markup. Reproduced and verified under a nested niri session: before the
fix RTMIN+n toggled the state class but left the sleep glyph unchanged;
after it the glyph flips as expected, and byte-identical updates are
still skipped.
#5158 (6672e924) moved connectToSocket() off the constructing thread and
into the detached IPC worker's own try/catch, so a missing NIRI_SOCKET no
longer throws out of IPC::IPC(). That was needed to fix#5117 (the worker
should reconnect instead of dying when an established stream drops), but
it also meant the very first connection attempt can never fail anymore.
Factory::makeModule()/Bar::getModules() rely on that constructor throwing
to disable a module it can't construct. With niri/workspaces and
niri/window always constructing successfully now, they get added to every
bar regardless of which compositor is actually running, showing up as a
permanently-empty widget next to the real workspace modules under
Hyprland/Sway.
Restore the old semantics for the first connection: connectToSocket() runs
synchronously in IPC::IPC() again, so a missing socket still throws and
the module gets disabled as before. Only a drop *after* that succeeds
falls into the retrying reconnect loop, preserving the #5117 fix.
Extend the pow_format spec parser beyond alignment so configs can shape the
rendered number, not just pad it. A forced scale (#, k, M, G, T, P) pins the SI
prefix instead of auto-selecting it and, since the author then knows both scale
and unit, hides the prefix and unit by default; U brings the unit back, u hides
it in auto mode, and i forces integer display. b and B override the decimal or
binary base independently of the call site, and a trailing width now fixes the
coefficient field when a scale is forced, overflowing to '#' when it does not
fit.
The old '>' and '<' branches rendered via fmt::format("{}", s), which discarded
every new field on the recursive call; they now build from a single render path
that reads the current formatter, so modifiers survive alignment. The auto path
is byte-identical to before, verified against the previous implementation.
Adds a Catch2 suite covering each modifier alone and combined, plus the
backward-compatible alignment cases, and documents the modifiers in the network
and disk man pages.
Move WAYBAR_OUTPUT_NAME injection into the parent-provided spawn environment and strip logging and setenv() out of the GLib child-setup hook.
That keeps the helper's post-fork path limited to the process setup it actually needs, which is a safer fit for sanitizer-heavy platforms such as FreeBSD.
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
Run clang-format on the changed C++ files and fix the clang-tidy findings introduced by the custom command migration.
The only codegen-relevant change here is switching the new res assignments in custom.cpp to designated initializers. The rest is formatting only.
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
Stop the interval worker from waiting synchronously on every pid in pid_children_ before it refreshes the module.
Switching this reap pass to waitpid(..., WNOHANG) keeps the worker responsive when an older event-triggered child is still running, while still removing children that have already exited.
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
Replace the custom module's continuous getline() worker with the new GLib-backed command stream helper.
This moves line delivery, child exit handling, and restart scheduling onto the main loop so continuous commands no longer depend on a blocking FILE* read inside SleeperThread.
The behavior is kept aligned with the old module semantics: stdout lines still emit updates, non-zero exits still surface as errors, and restart-interval still respawns the command.
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
Add focused coverage for the new GLib command stream helper.
These tests verify that complete lines are emitted as they arrive and that EOF flushes a final unterminated line without duplicating a newline-terminated one.
That behavior is the contract the custom module will rely on when its continuous command handling moves onto this helper.
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
Add a small GLib-backed helper for command stdout that integrates with the main loop instead of blocking on getline() in a worker thread.
The helper keeps the existing child setup semantics used by Waybar commands, including process groups, parent-death signaling, and WAYBAR_OUTPUT_NAME propagation.
This is the foundation for moving long-running custom commands away from manual poll/read logic in the module itself.
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
PR #4190 (merged as 93d85a0) reworked getNetworkState() so the rfkill
"disabled" state is evaluated whenever the module has no carrier. Because
the module always watches an RFKILL_TYPE_WLAN switch, a wired ethernet
module whose cable is unplugged (carrier lost) would return "disabled"
instead of "disconnected" whenever the system's WLAN radio happened to be
rfkill-blocked. With no format-disabled configured, that state falls back
to plain "format", so the interface kept looking connected after unplug.
rfkill only concerns wireless radios, so only honor it when there is no
interface at all or the current interface is actually wireless (detected
via /sys/class/net/<if>/phy80211 or /wireless). A wired interface that
lost its carrier now correctly reports "disconnected", while wifi rfkill
display from #4190 is preserved.
Fixes#4364.
When sway's event-subscription send buffer overflows during an event
flood, sway closes the client connection. The sway IPC event worker
(SleeperThread running handleEvent -> recv) then threw on every
iteration and the SleeperThread immediately re-invoked it, leaving the
sway modules broken while busy-looping on a dead socket and pegging a
CPU.
Mirror the niri backend's reconnect loop: on a read/EOF/parse error from
the event socket, close the old connection, back off for a couple of
seconds (so we don't busy-spin), re-open the socket and replay the same
subscriptions, then resume. A running_ flag set at the start of teardown
makes the worker bail out cleanly instead of reconnecting to a socket
that is being closed on purpose. The IPC message protocol and event
parsing are unchanged.
Fixes#3166.
The backlight module only enumerated and monitored the udev "backlight"
subsystem, so keyboard-backlight LEDs in the "leds" class (e.g.
white:kbd_backlight, platform::kbd_backlight) were never discovered and
the module fell back to the default when pointed at one.
Enumerate and monitor the "leds" subsystem in addition to "backlight".
Those LEDs expose the same brightness/max_brightness attributes, so the
read path is unchanged. Each device now records its subsystem so the
login1 SetBrightness call targets the correct one. Automatic device
selection still prefers a "backlight" device and only falls back to a
"leds" device when named explicitly or when no screen backlight exists.
Fixes#2848.
Previously the wireplumber module connected to PipeWire once in its
constructor and had no handling for the connection being lost. When
PipeWire or the wireplumber service restarted (or crashed), the module
went stale/blank and never recovered until Waybar itself was restarted.
Connect to the WpCore "disconnected" signal and, on disconnect, schedule
a bounded main-loop retry (Glib::signal_timeout) that tears down the now
invalid core/object-manager/mixer-api references and rebuilds the whole
connection from scratch, re-running the async API and object-manager
setup. Connection setup/teardown is factored into setupConnection() and
teardownConnection() so startup and reconnect share one code path.
The reconnect timer is cancelled in the destructor and the existing
isModuleAlive() registry guard still protects in-flight async callbacks,
so teardown during a pending reconnect stays safe.
Fixes#2882.
Blessed from the CI artifact of the passing headless run (Waybar / CI smoke
test / 12:34 on a pinned DejaVu Sans bar). Regenerate from a new artifact when
an intended visual change trips the comparison — see test/smoke/README.md.
The static custom modules used `exec: echo` (empty output), so the modules
rendered nothing and the bar strip was blank. Have exec print the label text
and use the default `{}` format.
Launch the real Waybar binary inside a headless, software-rendered sway
compositor and verify it actually runs and renders — something the unit
tests and build jobs never do.
- test/smoke/run.sh: boots sway (WLR_BACKENDS=headless, pixman), starts
waybar, asserts it stays alive with no fatal log, optionally grabs a
screenshot with grim
- level 1: real modules (clock/cpu/memory/disk) load without crashing
- level 2: deterministic config is screenshotted and checked to be non-blank
- level 3: screenshot compared to test/smoke/reference.png (fuzz 8%,
800px tolerance); screenshot + diff uploaded as artifacts
- .github/workflows/smoke.yml runs it on push/PR
The reference image must be blessed from a CI artifact (see test/smoke/README.md).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- README: quick-links bar, compositor-support table, module list grouped by
theme with links to the wiki, getting-started section, collapsible
dependency blocks, updated badges (CI, release), and a note that module
docs live in man/ (auto-synced to the wiki)
- add CONTRIBUTING.md (dev build, code style, docs workflow, PR checklist)
- add issue templates (bug report, feature request) and a PR template
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The name-vanished playerctl callback called event_box_.set_visible(false)
directly. On resume from suspend this runs in a re-entrant / torn-down
state and crashes in Gtk::Widget::set_visible. Only clear the player and
dp.emit() from the callback; update() (on the main thread) computes
visibility from player state and hides the module when there is no player,
matching the other hardened handlers.
Fixes#5124.
The WirePlumber module registers three async callbacks (onDefaultNodesApiLoaded,
onMixerApiLoaded, onPluginActivated) that receive a raw self pointer with a NULL
GCancellable. WirePlumber cannot withdraw an in-flight callback, so if the module
is destroyed before a queued callback fires (e.g. a temporary output/bar is removed
while a component load is still pending, or during an audio route transition), the
callback dereferences the freed self, causing heap corruption / a crash.
Guard each of these callbacks with isModuleAlive(), which checks the existing static
modules registry. The destructor already removes this from the registry before any
teardown, so a missing entry means self is dangling and the callback bails out
without touching it.
A GCancellable cannot fix this cleanly here: every callback dereferences self on its
first line, and wp_core_load_component completes via a WpTransition (not a GTask), so
the cancellable is not recoverable from the GAsyncResult either. The liveness check
must not touch self at all.
Fixes#3974.
On reload the GApplication is recreated but the default main context (and any
queued PRIORITY_HIGH_IDLE createBarsBatch source) survives. pending_outputs_
was left holding dangling waybar_output* into the just-cleared outputs_ list;
createBarsBatch filters by address, which can mis-match once a freed slot is
reused. Clear pending_outputs_ and reset bars_scheduled_ in bindInterfaces so
the next run batches from a clean state. Mitigates the dangling-pointer path of
#4129 (the cross-process app-id race in #4117 is separate).
Some pandoc versions (incl. the pinned 3.5) render scdoc bullet lists as a
series of `> ·` blockquotes instead of a Markdown list, which made the STYLE
sections look broken. Post-process the generated Markdown to collapse that
artifact back into real `- item` bullets, independent of the pandoc version.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The MPD state machine drives all connection attempts from Glib::signal_timeout
callbacks, which run on the GTK main loop. tryConnect() called
mpd_connection_new() with the user-facing timeout_ (up to 30s by default), so an
unreachable server blocked the whole bar for the full connect timeout.
Bound the connect attempt to a short fixed timeout (2000 ms) so a dead server
fails fast, then restore the configured timeout_ for subsequent command reads so
slow-but-alive servers are unaffected.
Fixes#1186.
wlr/taskbar reads on-click* config values (close, minimize, maximize,
fullscreen, minimize-raise, activate) directly as internal actions in
Task::handle_clicked, but never adopted the eventActionMap_/doAction
mechanism. As a result AModule::handleUserEvent additionally forkExec-ed
the same value as a shell command, e.g. on-click-middle: "close" ran the
action and then failed with "sh: line 1: close: command not found".
Register the taskbar built-in action names in eventActionMap_ so they are
recognized as module actions, and skip the shell forkExec in
handleUserEvent when the configured value is a recognized module action.
Non-action values are still run as user shell commands.
Fixes#3284.
When a tray item exports menu accelerators (e.g. Mattermost), libdbusmenu-gtk
calls gtk_widget_set_accel_path() with a NULL accel group because the
DbusmenuGtkClient never had one assigned. This raises a Gtk-CRITICAL that
corrupts menu state, and aborts Waybar when running under
G_DEBUG=fatal-criticals.
Assign a fresh GtkAccelGroup to the client right after the menu is created,
before it is populated or shown.
Fixes#5142.
Improve the generated wiki pages:
- normalize definition-style option blocks (*name*: / typeof: / default:)
into scdoc tables, so every module renders options as a clean table like
the table-style pages (Bluetooth, Network, ... no longer a wall of text)
- drop pandoc's empty leading table-header row so the real header shows
- pin pandoc to 3.5 in CI (the distro 2.x man reader mangled lists into
blockquotes); matches local output
- prepend a visible "auto-generated from master, do not edit here" note to
every page (was an invisible HTML comment)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When no explicit device-path is configured, a multi-node keyboard can
expose several event devices where only one actually toggles the lock
LEDs. Previously update() read state from a single arbitrary device
(libinput_devices_.begin()), which often picked a node whose EV_LED
values never change, leaving the indicator stuck. Iterate all devices
and OR their NUML/CAPSL/SCROLLL values together so a lock is reported
on if any device reports it on. The single-device path is preserved
when device-path is set.
Fixes#2215.
For a module configured node-type "Audio/Source", node_id_ and
source_node_id_ resolve to the same source node, so its primary mute
state is stored in muted_. update() unconditionally mapped muted_ to the
muted/sink-muted classes and source-muted only to the secondary
source_muted_ flag, so a source module could never receive source-muted
-- only the sink classes.
Gate the mute-class selection on the configured node-type: a source-type
module drives source-muted from its primary mute state, while a sink-type
module keeps muted/sink-muted for its sink and source-muted for the
secondary default source it tracks for {format_source}. The primary node
still feeds {volume} via updateVolume, so source-widget volume rendering
is unaffected.
Fixes#4523.
The mixer-api is configured with the linear scale (0), so volume_ holds
the raw linear gain. The perceptual "cubic" value shown by wpctl and
exposed as {volume} is cbrt(linear), but update() computed pow(volume_, 3)
instead. Cubing under-reads every volume below max and collapses small
linear gains to 0% -- which is why the default Bluetooth sink (whose
normal levels map to low linear gains, e.g. wpctl 0.55 -> linear 0.166)
displayed 0% while wpctl reported a normal, unmuted volume.
Replace the inverted conversions with the correct cube-root/cube pair in
the display path, the scroll-scale conversions (cubic / cubic_percent),
and the max-volume ceiling mapping so scrolling and the cap stay
consistent with the corrected {volume}.
Fixes#5159.
An unsupported specifier (e.g. the %-I / %OI no-leading-zero padding
modifiers, which the date/std::chrono formatter does not implement) threw out
of update() and the whole clock module failed to load. Catch it, warn once,
and fall back to {:%H:%M} so the bar still comes up. Addresses #1469.
Addresses review: a zero interval_ must stay reserved for modules whose
default interval is already 0 (event-driven). Periodic modules (clock,
simpleclock, pollers) would otherwise do % interval_ (modulo by zero) or
sleep_for(0) in a tight loop. interval:0 on a periodic module now falls back
to its default interval.
The niri IPC worker slept 1ms per event and never reconnected. Under an
event burst the per-event cap back-pressures the socket, niri fills its
send buffer and drops the stream; read_line then returns false, the
detached thread exits and the module freezes permanently.
Remove the per-event sleep so events drain as fast as they arrive, and
wrap the socket setup and read loop in a reconnect loop that backs off
and re-establishes the stream on drop. A running_ flag lets the thread
exit cleanly on teardown.
Fixes#5117.
connectContext() throws std::runtime_error when pa_context_connect() fails.
It was called directly from contextStateCb (the libpulse mainloop thread,
running pure-C callback frames) on the PA_CONTEXT_FAILED reconnect path, so on
a pipewire/pulse restart the exception unwound across the C callback boundary
and triggered std::terminate/SIGABRT.
Add reconnectContext() noexcept which wraps connectContext() and logs failures
instead of throwing, and use it from the callback. Guard against the
FAILED -> connect -> FAILED recursion/busy loop with a reentrancy flag. The
constructor-time connectContext() still throws as before.
Fixes#5141.
On device unplug the inotify IN_DELETE handler removed the libinput
device and unref'd it before erasing the entry from libinput_devices_.
A repeated IN_DELETE event for the same path (observed as the
"has been removed" log line printed twice) could reach an
already-unlinked device and trigger a libinput list_remove assertion
abort.
Erase the map entry first (under devices_mutex_) so a second delete for
the same path is a no-op, then call libinput_path_remove_device() and
libinput_device_unref() exactly once per device pointer.
Fixes#5143, #4443, #4566.
onCmd() runs on the sway IPC worker thread and called updateAppIconName(),
which touches the global Gtk::IconTheme cache. Concurrent access with the
main thread's draw (propagate_draw -> gtk_icon_theme_has_icon ->
g_hash_table_lookup) races and can segfault, notably on multi-monitor and
focus changes.
Move the icon-theme lookup into Window::update(), which runs on the main
thread via dp.emit(), and only store app_id_/app_class_ in onCmd().
Fixes#4108.
set_current_layout() mutated label_'s GTK style context (remove_class/
add_class) while being called from the sway IPC worker thread via
onEvent(). Off-main-thread GTK widget mutation caused a SIGSEGV.
Record only the target layout in set_current_layout() and apply the
matching CSS class in update(), which the dispatcher runs on the GTK
main thread. A new applied_class_ member tracks the currently applied
class so update() can swap it. The shared layout_/applied_class_ state
is guarded by the existing mutex_.
Fixes#3702.
The Portal constructor synchronously auto-starts org.freedesktop.portal.Desktop
via a Gio::DBus::Proxy. If that service fails or crashes on start it throws a
Glib::Error, which was previously uncaught and terminated Waybar. Wrap the
construction in a try/catch, log a warning and leave portal as nullptr on
failure, and null-guard every dereference so a missing portal simply disables
light/dark appearance detection instead of crashing.
Fixes#3140, #3601.
Building a zoned_time/zoned_seconds from a local_time throws
ambiguous_local_time during the DST fall-back hour and nonexistent_local_time
across the spring-forward gap. update() runs this every minute with the
tooltip enabled by default and has no try/catch, so Waybar aborts every
minute during a DST transition. Pass choose::earliest at each construction to
resolve deterministically instead of throwing.
Fixes#2615; resolves the recurring DST-crash duplicates #5006, #5018, #5063,
#5096, #3024.
- parseOutputJson() passed script text/alt/tooltip straight to Pango/GTK; an
invalid-UTF-8 byte aborted the bar in g_utf8_collate. Validate/make_valid
like parseOutputRaw already does. Fixes#2829.
- restart-interval:0 was floored to 1ms, respawning the script ~1000x/s and
starving the main loop; a non-positive restart-interval now stops instead.
Part of #4842.
std::max(1L, interval*1000) turned a user's explicit "interval": 0 into a
1ms periodic refresh, i.e. a ~1000x/s busy loop that starves the GTK main
loop and leaks memory (mpris RSS growth, missing tooltips, frozen updates).
An explicit 0 now stays the 'no periodic refresh' sentinel. Fixes#4987,
#4842; helps #4864, #4917, #4998, #5145.
The format-<short_description>[-<variant>] override branches only passed a
positional arg, so a format using {short}/{long}/{variant} threw 'argument
not found', which disabled the whole module. Now supply the same named args
as the fallback/tooltip branches. Fixes#5120.
refresh() called config_["signal"].asInt() unconditionally on every RT
signal; a non-integer "signal" value throws Json::LogicError and aborts
Waybar. Matches the guard already present in custom/image/idle_inhibitor.
Fixes#3514.
generate.py now keeps _Sidebar.md in sync: any Module:-* page from the
mapping that is not yet linked is inserted alphabetically into the
Modules list. Existing entries (custom labels, nested sub-entries,
hand-written non-module links) are left untouched, so adding a module no
longer requires editing the sidebar by hand.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add tooling that makes the scdoc man pages the single source of truth and
regenerates the wiki module pages from them:
- .github/wiki/mapping.json: man-page -> wiki-page mapping (aggregation-aware)
- .github/wiki/generate.py: scdoc -> pandoc -> gfm, strips man-only sections,
concatenates aggregated pages, appends optional extras/<Page>.md
- .github/wiki/extras/: hand-kept appendices (screenshots) with no man equivalent
- .github/workflows/wiki.yml: on push to man/** (or the tooling), regenerate
and push the wiki; other wiki pages are left untouched
Covers all 65 man pages -> 45 wiki pages (5 new: GPS, Inhibitor, Mango, Menu,
WWAN). Non-module and hand-written wiki pages are never modified.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Port information that previously lived only in the GitHub wiki into the
authoritative scdoc man pages, so the man pages become the single source
of truth for module documentation.
33 man pages enriched (options, format replacements, actions, style
selectors, troubleshooting and implementation notes). Additions were
verified against the source; stale/incorrect wiki entries were
deliberately not ported. All pages still compile with scdoc.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A full man<->code consistency audit surfaced options, format placeholders
and CSS classes that were implemented but undocumented, documented but not
implemented (some causing fmt crashes when copied from examples), and
defaults that disagreed with the code. This aligns the docs with the code
and fixes a few genuine code gaps.
Docs:
- Add the missing waybar-user(5) man page (and register it in meson.build)
- Document previously-undocumented options/placeholders/CSS across many
modules (custom image-path/image-name/icon-size, graph_type/width/
datapoints; battery smooth-power; wireplumber format-source/only-physical;
mpris {position}/prefer-album-artist; network {signalStrengthApp}/compact
bandwidth; pulseaudio {source_volume}/{source_desc}; upower {temperature}/
{model}/{native-path}; wwan {power_state}/{imei}; tray ignore-list; and
many CSS state classes: .sink-muted, .source-muted, .workspace-hover, etc.)
- Correct documented defaults to match the code (hyprland format {name},
gamemode {count}, cpu-graph interval 5, cava input_delay 4, niri taskbar
icon-size 16, disk/gps/wayfire formats, menu-actions object type, ...)
- Remove placeholders/options that do not apply (custom-graph {icon}/format/
format-icons/rotate) and fix crashing examples (wwan {mode}, gps
format-no-fix); note cava background/foreground/continuous_rendering are
cava-config-file options
Code:
- bluetooth: accept the documented `controller` key as a synonym of
`controller-alias` (the option was silently ignored)
- mango/workspaces: supply the documented `{name}` fmt arg (was missing ->
fmt::format threw)
- privacy: read `tooltip` as a bool (was guarded on isString(), so the
documented `tooltip: false` was silently ignored)
All man pages validated with scdoc 1.11.4. Not compiled locally (no gtkmm);
C++ build relies on CI.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On Debian sid, `dependency('systemd')` needs systemd.pc, which is shipped by
the `systemd-dev` package, not `libsystemd-dev`. Add it so the forced
-Dsystemd=enabled configure step succeeds.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The linux matrix ran every distro with features in `auto` mode, so any
module whose dependency was missing from the image was silently skipped
and never compiled. Three modules (privacy/pipewire, gps, wwan) were built
by no CI job at all, and several others only on one or two distros.
Add a dedicated `build-full` job (Debian image) that installs the missing
dependencies inline and force-enables every optional feature, turning a
broken include or a missing dep into a hard build error instead of a silent
skip. Drop `debian` from the auto matrix since `build-full` uses the same
image and builds a strict superset.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
Adds an ethernet link speed ({linkSpeed}) read from
/sys/class/net/<iface>/speed, plus a skip-decimal option on pow_format
to drop a trailing .0. Rebased onto master's store-based network
formatting; existing compact bandwidth args updated for the new
pow_format signature.
Adds a per-workspace app-icon taskbar (Workspace class) to the
niri/workspaces module. Rebased onto master: integrated with the
existing window-rewrite feature so the {windows} and {total} format
replacements continue to work alongside the new taskbar.
Rebased onto current master and fixed the privacy module build:
- privacy_item.hpp no longer includes privacy.hpp (it does not use the
Privacy class); this broke the circular include that left PrivacyItem
undeclared when privacy.hpp was reached first from privacy_item.cpp.
- privacy_item.cpp now includes gtkmm/label.h explicitly, since Gtk::Label
was previously only pulled in transitively via privacy.hpp.
Adds format-window-separator, window-rewrite and window-rewrite-default
options plus the {windows} format replacement to the niri/workspaces
module (rebased onto master; coexists with ignore-workspaces).
The ModemManager libmm-glib library installs its pkg-config file as
mm-glib.pc (not libmm-glib.pc), so dependency('libmm-glib') never
resolved and the nix-flake-check build failed with the wwan module
enabled.
- update(): only hide the module when hide-disconnected is set AND the
modem is not in the CONNECTED state, instead of hiding it unconditionally
(the module was invisible by default).
- free the gchar* returned by mm_modem_dup_physdev/
mm_modem_dup_equipment_identifier/mm_sim_dup_operator_name.
- meson.build: drop duplicate libgps dependency, install the wwan man page.
- man: document hide-disconnected (replacing the unimplemented
hide-failed/hide-disabled/hide-not-registered options).
- remove stray empty subprojects/.wraplock.
Move resolveTooltipFormat() (and add resolveFormat()) from ALabel down into
AModule, and add a generic updateTooltip(Gtk::Widget&, ...) helper. This lets
modules that are not ALabel-derived reuse the shared tooltip logic instead of
re-implementing it. Migrate gamemode (the only non-ALabel module reading
tooltip-format) to updateTooltip(box_, ...). ALabel inherits the resolvers.
Migrate cpu, cpu_usage, cpu_frequency and memory to the generic
updateLabelAndTooltip/ForState helper so label and tooltip rendering go
through the dedup-aware setters and shared tooltip-format resolution.
Add store-accepting overloads of the helper to ALabel for modules that
build a dynamic fmt argument store (per-core cpu stats). cpu keeps its
tooltip-format-<state> selection via the state overload; the others keep
their existing tooltip-format-only behavior.
Migrate network, pulseaudio, wireplumber, jack and power_profiles_daemon
to the generic ALabel tooltip helpers:
- jack and power-profiles-daemon use updateLabelAndTooltip() since their
label/tooltip share a single arg set and a format-string tooltip default.
- network, pulseaudio and wireplumber keep their rich in-module format
selection but build a single fmt arg store, render the label/tooltip
through the dedup-aware setLabelMarkup()/setTooltipMarkup() setters and
resolveTooltipFormat(), preserving their custom fallbacks (label text,
sink/node description) and visibility handling exactly.
simpleclock: migrate to updateLabelAndTooltip (label + tooltip share the
localtime arg; default tooltip falls back to the label format).
systemd-failed-units: use setLabelMarkup/setTooltipMarkup dedup setters;
label/tooltip formats are selected by different conditions so the combined
helper does not apply.
upower: use setLabelMarkup for the label; tooltip stays a custom GTK widget.
mpd already uses the dedup setters (label/tooltip use different truncated
args), so no change.
Migrate idle_inhibitor, sway/language, sway/scratchpad,
hyprland/language and hyprland/window to the shared label/tooltip
setters. idle_inhibitor uses updateLabelAndTooltipForState; the others
keep their custom label/tooltip resolution (trim, precomputed tooltip,
rewriteString, per-lang/variant formats) and switch to the dedup-aware
setLabelMarkup/setTooltipMarkup.
Switch direct label_.set_markup/set_tooltip_markup calls to the
deduplicating setLabelMarkup/setTooltipMarkup helpers in clock, custom
and mpris, keeping their custom tooltip construction logic intact.
taskbar is left untouched: its tooltip lives on per-task Gtk::Button,
not on an ALabel label_.
Modules duplicated the same boilerplate to format their label and tooltip
(read tooltip-format, fmt::format, set_tooltip_markup) across ~29 modules.
Add updateLabelAndTooltip(labelFormat, tooltipDefault, args...) and its
state-aware variant updateLabelAndTooltipForState(state, ...) to ALabel: they
build a single fmt arg store, render the label and the resolved tooltip format
(tooltip-format[-state] or default) through the dedup-aware setters, honoring
the tooltip toggle. resolveTooltipFormat() centralizes the format resolution.
Migrate temperature and disk as the first adopters.
queryMPD() was removed from Context by the master-side dedup refactor
(perf(mpd): avoid duplicate playing-state updates). fetchState() is
already called earlier in on_io and emit() updates the GUI, so the
redundant queryMPD() call is dropped, matching the identical resolution
applied to Playing::on_timer on master.
Keep the {driver} placeholder working and support older
power-profiles-daemon versions that only expose a single Driver DBus
property, while still reading the new CpuDriver/PlatformDriver fields.
- Read the legacy Driver property and fall back to it when CpuDriver or
PlatformDriver are missing (older daemon).
- Derive {driver} from CpuDriver when the daemon no longer exposes
Driver (recent daemon), so existing configs using {driver} keep
working.
- Align the default tooltip-format in the constructor with the
documented default.
The constructor created output_status_ and seat_status_ without adding any
listener, then handle_show() recreated both (leaking the constructor-created
objects) before destroying status_manager_. Additionally seat_status_ was
never destroyed in ~Tags().
Create these objects lazily (with listeners) only in handle_show(), and
destroy seat_status_ in the destructor.
Compile the icon-extraction std::regex objects once via static locals
instead of rebuilding them on every update() of every AIconLabel-derived
module (custom, mpris, ...). Also default-initialize label_contains_icon
to false at its declaration.
The tooltip selection block redundantly reassigned tooltip_format to
config_["tooltip-format"], overwriting the state-specific format (and
emptying it when no global tooltip-format was set). A lone
tooltip-format-<state> therefore produced an empty tooltip.
Consolidate the tooltip handling into a single block that prefers
tooltip-format-<state>, falls back to the global tooltip-format, then to
the computed default tooltip, reusing the existing format arg store.
The zero-on-mute display_value = min_ assignment was nested inside the
!previously_muted transition branch, so subsequent updates while still
muted re-displayed the real volume. Apply it on every update as long as
the target is muted, keeping the CSS class add/remove on transitions only.
std::setfill is templated on the fill char type, which must match the
Glib::ustring FormatStream's char type. That type is wchar_t with
libstdc++/glibmm but char with libc++, so no fixed literal (' ' or L' ')
compiles everywhere. The stream's default fill is already a space, so
drop std::setfill entirely; std::setw/std::left/std::right are
char-type-agnostic and keep the exact padding behavior.
Reporting a full battery on AC as 'Plugged' is now opt-in via the
'full-at-plugged' config option (default false), so existing format-full /
tooltip-format-full setups keep working. Also documents the option.
Only stretch buttons and ellipsize titles when the new boolean 'expand'
option is enabled and the bar is horizontal. Restores the previous
content-based sizing as the default and avoids truncating every title
to an ellipsis on vertical bars.
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.
Replace the getSocket1JsonReply("reload") call in onSpecialWorkspaceActivated
with the module's normal local refresh path: the state update to
m_activeSpecialWorkspaceName is picked up by the update() render (triggered by
the dp.emit() already issued at the end of onEvent), which re-evaluates active
state, icons and CSS classes. This avoids reloading the entire Hyprland config
on every special workspace activation while keeping the special-active icon and
CSS improvements.
The hover-check Glib::signal_timeout re-arms every 50ms and is not owned by
the Workspace. Since Workspaces are destroyed at runtime, a workspace removed
while its check is armed would let the timeout fire on freed memory (use-after-
free, also touching the destroyed m_button). Add a destructor that calls
stopHoverCheck() to disconnect the source.
The active-layout event payload is `KEYBOARDNAME,LAYOUT`, and kbName was
taken as everything before the first comma. Keyboard names reported by
Hyprland can themselves contain commas (e.g. some MSI keyboards), so a
`keyboard-name` configured with a comma never matched the truncated value
and the module stopped updating.
Match the configured keyboard-name as a full prefix (followed by the ','
separator) instead. Layout parsing already handles commas via the
last-comma-before-parenthesis heuristic, so only the keyboard-name filter
needed fixing.
Addresses the keyboard-name case originally reported by @tirsek in #3406.
Currently, modules polling hardware nodes continuously wake up kernel drivers even when the monitor is asleep (e.g., DPMS off), flooding dmesg.
- Adds 'disable-on-sleep' config flag
- Hooks GTK window map/unmap signals to track DPMS state
- Propagates suspend/resume calls to AModule worker threads
Previously, format-icons with N icons would divide the 0-100 range
into N equal parts. This made it impossible to define custom ranges
like 0%, 1-69%, 70-100%.
This adds support for an alternative object syntax in format-icons:
"format-icons": [
{ "icon": "▁", "max": 0},
{ "icon": "▅", "max": 69 },
{ "icon": "▇", "max": 100 }
]
The array is iterated in order and the first icon whose max value
is >= the current percentage is returned. If no threshold matches,
the last icon is used as fallback. The existing string array syntax
remains fully supported and unchanged.
Applies to both getIcon overloads, affecting all modules that use
format-icons (battery, pulseaudio, network, backlight, etc.).
Closes#5150
Add opt-in bar-css-states option to wlr/taskbar module.
When enabled, the module tracks the active workspace and updates CSS
classes on window#waybar to reflect the current window state:
- toplevel-active: when a window is focused
- toplevel-maximized: when any non-minimized window on active workspace is maximized
- toplevel-minimized: when the active window is minimized
- toplevel-fullscreen: when any non-minimized window on active workspace is fullscreen
This feature requires compositor support for ext-workspace-v1 protocol
to correctly track workspace membership. Without it, classes fall back
to the active application state only.
Also includes active-only option implementation.
Changes:
- Add ext-workspace-v1 protocol support for workspace tracking
- Implement WorkspaceState tracking in Taskbar class
- Add workspace management lifecycle (create/remove/done)
- Add bar CSS class update logic with aggregation
- Add visible() getter to Task class
- Implement active-only task visibility control
- Update man page with documentation and CSS examples
Signed-off-by: iamcheyan <iamcheyan@users.noreply.github.com>
Fixes a bug where removing a hot-plugged device mid-loop causes a filesystem error that turns into a fatal runtime error that crashes waybar entirely, changed to warning instead of runtime error.
### Problem
Waybar segfaults on startup when `niri/workspaces` is the only configured
module (default options). Right after the first `WorkspacesChanged` event:
Thread 1 "waybar" received signal SIGSEGV
#0 gtk_label_set_markup ()
#1 waybar::modules::niri::Workspaces::doUpdate () at src/modules/niri/workspaces.cpp:106
### Root cause
[doUpdate()](cci:1://file:///home/lj/Downloads/Waybar/src/modules/niri/window.cpp:27:0-90:1) set the label markup via:
static_cast<Gtk::Label*>(button.get_children()[0])->set_markup(name);
The button's child *is* a valid `GtkLabel`, but gtkmm's `get_children()`
returns it wrapped as a generic `Gtk::Widget` (confirmed: `dynamic_cast`
to `Gtk::Label*` yields `nullptr`). The unchecked `static_cast` then
performs an invalid downcast, producing a corrupt pointer whose `gobj()`
is garbage (`0x1`), so `gtk_label_set_markup()` dereferences it and crashes.
This is reliably triggered when no other module has instantiated a
`Gtk::Label` yet (so the `Gtk::Label` wrapper isn't registered), which is
exactly the case for a `niri/workspaces`-only bar. The same idiom exists in
`sway/workspaces` and `wayfire/workspaces`; it's masked there because typical
configs include other label-using modules.
### Testing
- Reproduced the crash on 0.15.0 / current `master` with a minimal
`"modules-left": ["niri/workspaces"]` config under niri.
- After the fix: no crash; three workspace buttons render with correct
names/labels (`niri-workspace-1/2/3`).
When Host::proxyReady fails to create the SnWatcher proxy (e.g. because
the Watcher has not finished exporting /StatusNotifierWatcher yet), the
cancellable is left set, causing nameAppeared to early-return on every
subsequent event (see the // TODO marker). The Host is then stuck
without a watcher, and the tray module reports
'No such object path /StatusNotifierWatcher' until the bar is fully
restarted.
Clear the cancellable on non-CANCELLED errors and schedule a single
delayed retry of nameAppeared. The guard `watcher_ != nullptr` skips
the retry if a parallel call already succeeded.
Closes#3468
Fix an issue in the MPD module where text failed to render
when raw tags containing markup characters were truncated
mid-sequence after sanitization,
resulting in fragmented XML entities (such as `&` cut into `&am`).
Resolve this by shifting the order of operations to
truncate the raw strings *before* running `sanitize_string`.
To keep `setLabel` decoupled and clean,
we introduce helper methods (`getArtistStr`, `getAlbumArtistStr`,
`getAlbumStr`, `getTitleStr`) inside the `MPD` class,
matching the architectural style of MPRIS.
Additionally:
- Introduce support for the customizable `ellipsis` config option
(defaulting to `…`).
- Switch the truncation logic of the MPD module to be
visual-width-aware using the newly extracted
`waybar::util::utf8_truncate`.
- Decouple the tooltip text from the main label's truncated text.
The tooltip now displays un-truncated, complete metadata
(properly escaped), providing a significantly better user
experience.
Fix an issue where the mpris module's tooltip failed to render
when track metadata contained unescaped XML/HTML markup characters.
This occurred because
the mpris module formatted raw strings into the tooltip template
before passing the final string directly to `set_tooltip_markup()`,
triggering GTK/Pango parsing warnings and rendering failures.
Resolve this by wrapping tooltip metadata fields
in `Glib::Markup::escape_text` before formatting,
ensuring Pango-compliant strings are always delivered to the GTK
tooltip markup.
Move the UTF-8 visual-width measurement and truncation helper functions
from the mpris module to a common utility
(include/util/utf8_string.hpp and src/util/utf8_string.cpp).
This decouples string truncation and width measurement
from the mpris module, allowing other modules (like mpd) to reuse.
The helper function `utf8_truncate` (formerly `truncate` in mpris)
and `utf8_width` is exported under the `waybar::util` namespace,
while the low-level `measure_and_truncate` (formerly `utf8_truncate`)
is encapsulated in an anonymous namespace in src/util/utf8_string.cpp
to avoid unnecessary API exposure.
No functional changes were made to the mpris module's behavior.
The handleScroll method unconditionally dispatched workspace cycling commands
and never checked for custom on-scroll-up/on-scroll-down config values.
Register the scroll handler when custom scroll commands are configured (even
without enable-bar-scroll), and delegate to AModule::handleScroll so user
commands are executed, matching the pattern used by other modules.
The taskbar currently requests enough width for every task title, which can push right-side modules off-screen when many windows are open. Allow task buttons to share the taskbar allocation and ellipsize title labels instead of forcing the whole module to grow.
This keeps neighboring modules visible while preserving the existing taskbar click actions and formatting behavior.
Co-authored-by: Cursor <cursoragent@cursor.com>
Concatenates all per-core icons into a single {icons} placeholder,
so configs work across machines with different core counts without
manually specifying {icon0}{icon1}...{iconN}.
Closes#4240
- Add "muted" class for styling the muted state.
- Add "zero-on-mute" boolean option to control slider position
when muted.
- Add "unmute-on-volume-change" boolean option to control whether
to automatically unmute when the volume changes.
devices) so transient events stop blanking layout_. Refresh layouts_map_
on "added"/"xkb_keymap" input events and union layouts across all
keyboards so new devices contribute their layouts. Release mutex_ before
the refresh sendCmd to avoid self-deadlock from the synchronous
signal_cmd emit.
When switching between workspaces rapidly (especially empty ones),
Hyprland sends createworkspace and destroyworkspace events slightly
out of order. This causes workspace buttons to render in wrong
positions for a split second before snapping to their correct spots.
Add a 7ms debounce timer that batches workspace events before
calling dp.emit(), preventing the visual glitch.
Fixes#4376
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
Adds the following:
- bandwidthDownByesCompact
- bandwidthUpByesCompact
Differences from normal version:
- drop the "/s" suffix
- drop the deciaml part if less than 1MB/s
Enable per-workspace styling by adding the workspace name as a
CSS class to each workspace button. The name is sanitized to a
valid CSS identifier: lowercased, non-alphanumeric characters
collapsed to hyphens, digit-leading names prefixed with "ws-".
Examples: "IDE" → .ide, "special:telegram" → .telegram, "1" → .ws-1
Allows users to style individual workspaces:
#workspaces button.telegram { color: #54a0e0; }
In order to obtain the RX and TX bitrates from the kernel netlink we need to open a second info socket to get the station data. The values are formatted like "1.9Gb/s" and then exposed as `rx_bitrate` and `tx_bitrate`.
This code is closely following the `iw` implementation.
The iso8601 flag previously controlled both the calendar grid layout
and the week number format as a coupled boolean. This adds a new
weeks-numbering option (iso/monday/sunday) that explicitly overrides
just the week number calculation method, independent of iso8601 and
locale settings. Existing behaviour is fully preserved when the option
is not set.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The pulseaudio/slider module had partial support for a "target" config
option but changeVolume() in the audio backend was hardcoded to only
modify sink volume. This adds full source volume control:
- Add AudioTarget enum (Sink/Source) to audio_backend
- Extend changeVolume() overloads with AudioTarget parameter
- Store and use pa_cvolume for source (with mono channel default)
- Add sourceVolumeModifyCb for source volume change confirmation
- Add "target" config option to the pulseaudio module for scroll control
- Document "target" in both pulseaudio and pulseaudio-slider man pages
Usage:
"pulseaudio/slider#in": { "target": "source" }
"pulseaudio#in": { "target": "source" }
Replace the inner loop with regex_replace. We're already using this
elsewhere and the performance difference isn't worth the loss of human
readability.
Use static const to pre-calc the search string.
If you have reload_style_on_change enabled, and use dynamic appearance
styling (style-dark.css and style-light.css), then changing the
appearance doesn't update the files that are being watched for reload.
Add a method to CssReloadHelper to change the CSS file being watched,
and also provide the CSS file as a parameter to the callback so setupCss
can be called on the right file when the file watcher triggers.
Allow selecting a hwmon device for the temperature module by its
name instead of a fixed hwmon path.
This avoids fragile configurations that break when new hwmon
devices are added and the kernel changes hwmon ordering.
The hwmon-name option is mutually exclusive with hwmon-path and
hwmon-path-abs to prevent ambiguous configurations.
Most modules support config fields like "format-<status>-<state>", so
the user can, for example, set a different format for batter when
unplugged _and_ under a given level. Wifi didn't support this. Change
the module to support format state based on signal strength.
It makes it find the first node that has adevice.id linked to the
default output if the default output doesn't have it (i.e. it is a
virtual output).
Useful if you want to control (and see) the volume of the actual output
and not the effect sink.
May need some polishing and/or style fixing, but it works.
Previously, if you were to open a special workspace on a different
monitor than it was created, the display for workspaces wouldn't update
properly\nI've implemented a somewhat hacky solution by just reloading
hyprland everytime onSpecialWorkspaceActivated is called.
The `wlr/taskbar` module had no way to control its container's horizontal
alignment. This change adds a `justify` config key (left|center|right) that
maps to Gtk::Align::START/CENTER/END and is applied via set_halign() on the
module's Gtk::Box. Default behavior remains unchanged (left).
niri v25.08 added window positions to IPC, so waybar actually has the
information stored in json around.
This handles the associated WindowLayoutsChanged IPC message to keep the
position up to date in memory, and adds a couple of format messages to
show the current column, so one can dislpay where we are in the
workspace (in the hope of improving this feeling of being lost...)
Ideally a visual representation of the workspace like the one
illustrated in the PR[1] would be even better but this will probably do
for now.
Link: https://github.com/YaLTeR/niri/pull/1265#issuecomment-2724621758 [1]
- Add the possibility to show the volume in linear, cubic, db, and
cubic percent (default for backward compatibility).
- Add the ability to change the scale for the scrolling action.
- Make the minimal step flexible to fit any scale.
The module can be configured to neither request the user nor the system
session.
In fact does this display non-valuable information to the user and can
be considered as abuse case of the configuration.
Therefore throw an exception when neither user nor system session are
requested.
Signed-off-by: Steffen Kothe <steffen.kothe@skothe.net>
Allows for different styles of workspace indicators (buttons) depending
on the output the workspace is located at.
Using this one can for example color the indicators depending on the
output making it easy to infer on which output a workspace is located.
Example config:
"sway/workspaces": {
"all-outputs": true,
"format": "{name}",
"output-classes": {
"DP-1": "output1",
"LVDS-2": "output2"
}
}
Example CSS:
#workspaces button.output1{
background-color: green;
}
#workspaces button.output2{
background-color: blue;
}
This commit adds a new optional config to the pulseaudio modules. It
maps sink names to other sink names so that if the current sink is a
key, the sink named by the value is considered to be the current sink
instead of it.
E.g.
"sink-mapping": {
"easyeffects_sink": "speakers_sink"
}
One can set different tooltips for different workspaces. Selection works
the same as it does for Icons.
Also refactored the `selectIcon` function to a more general
`selectString` function.
Man page is updated too.
4. Standard library headers (`<algorithm>`, `<vector>`, `<memory>`).
5. Blank line.
6. Other project headers (`"util/..."`, `"modules/..."`).
- Do not use `using namespace` in headers. In `.cpp` files it is acceptable for narrow scopes (e.g., `using namespace std::literals::chrono_literals;`).
- Headers that expose standard-library types in their public interface (e.g. `std::chrono::milliseconds` as a return type or `std::vector<T>` as a member) must `#include` the corresponding standard header directly. Do not rely on transitive includes from other headers.
## 5. Class & Module Design
### Base Class Patterns
- All UI modules ultimately derive from `AModule` (and often `ALabel` or `AIconLabel`).
- Use `Glib::Dispatcher` (via `waybar::SafeSignal`) to marshal work to the GTK main thread.
- Use `sigc::signal` for normal GTK++ signals.
- If a scope must not be interrupted by `pthread_cancel`, guard it with `waybar::util::CancellationGuard`.
### State / IPC
- Modules that talk to a compositor often implement a small `EventHandler` interface (`onEvent(...)`) and delegate to a singleton backend (e.g., `gIPC`).
### RAII
- Prefer `std::unique_ptr` with custom deleters over raw `new/delete` for C-API resources (see `ScopedFd`, `UdevDeleter`, `UdevDeviceDeleter`, `ScopeGuard`).
## 6. JSON Configuration
- Every module receives `const Json::Value& config` (usually as the first constructor argument).
- Always validate node type before reading:
```cpp
if (config_["sort-by-id"].isBool()) { ... }
if (config.isMember("window-rewrite-default") && config["window-rewrite-default"].isString()) { ... }
```
- Use `waybar::util::JsonParser` if you need to pre-process JSON with non-standard escape sequences.
## 7. String & UI Formatting
- Use `fmt::format` / `fmt::join` for all string composition.
- Use `fmt::dynamic_format_arg_store<fmt::format_context>` when building arguments dynamically.
- Custom `fmt::formatter` specializations are allowed for domain types (e.g., `Glib::ustring`, project enums).
- Sanitize arbitrary text before inserting into Pango markup with `waybar::util::sanitize_string`.
- Use `waybar::util::rewriteString` for user-configurable regex rewrites.
- Truncate UTF-8 safely with `waybar::util::utf8_truncate` / `utf8_width`.
- Throw `std::runtime_error` (or similar) for fatal initialization failures that should bubble up to `main()`.
## 9. GTK / Glib Patterns
- Prefer gtkmm-3.0 types (`Gtk::Button`, `Gtk::Label`, `Gdk::Pixbuf`, `Glib::RefPtr`, `Glib::ustring`) over raw C GTK APIs.
- Access the default icon theme through thread-safe wrappers if off the main thread (`DefaultGtkIconThemeWrapper`).
- Tooltips and labels should respect the module `tooltip` toggle (see `tooltipEnabled()` in `AModule`).
## 10. Platform Portability
- Isolate platform-specific code in dedicated files (e.g., `linux.cpp`, `bsd.cpp`).
- Use preprocessor guards for platform differences (`#if defined(__FreeBSD__)`, `#if defined(HAVE_LIBNL)`).
- Keep the common interface in a shared header or base class.
## 11. Thread Safety & Cross-Thread Communication
- GTK is strictly single-threaded. Never emit raw `sigc::signal` from background threads.
- Use `waybar::SafeSignal<T...>` to marshal events from worker threads to the GTK main loop.
- When a module manages background threads, use `std::mutex`, `std::recursive_mutex`, or atomic variables to protect shared state, and ensure the destructor joins or synchronizes with those threads before destroying resources.
## 12. Unsafe Patterns to Avoid
- Do not use `strcpy`, `strcat`, or `sprintf` into fixed-size buffers (e.g. `char buf[PATH_MAX]`). Prefer `std::string`, `std::vector<char>`, or `std::array` with bounds-safe operations.
- When passing a `std::vector<char>` buffer to a C API that expects a mutable `char*` string, always ensure the buffer is null-terminated and clamp the written length to `size() - 1`. Never use `std::copy` from an unbounded source into a fixed-size buffer.
## 13. Singleton Lifetime
- Singletons or objects with process-wide lifetime must not store references (`&`) or pointers to objects with shorter lifetime (e.g., configuration trees, GTK widgets, or bar instances) unless they are explicitly notified of destruction. Prefer storing configuration by value (`Json::Value`, `std::string`, etc.) if the singleton outlives the config loader.
@@ -29,13 +29,30 @@ The brightness can be controlled by dragging the slider across the bar or clicki
*device*: ++
typeof: string ++
The name of the preferred device to control. If left empty, a device will be chosen automatically.
The name of the preferred device to control. If left empty, a device will be chosen automatically.++
Both screen backlights (the udev *backlight* subsystem) and keyboard backlights (LEDs in the udev *leds* subsystem, e.g. *white:kbd_backlight*) are supported; name such an LED here to control it. When left empty, a screen backlight is always preferred for automatic selection.
*interval*: ++
typeof: uint ++
default: 1000 ++
The interval in milliseconds at which the brightness is polled and the slider is updated.
*expand*: ++
typeof: bool ++
default: false ++
Enables this module to consume all left over space dynamically.
*Note*: As well as the JSON configuration, the slider modules are special in
that they *require* styling to work. You *need* to set *min-width* and/or
*min-height* (depending on whether your slider is vertical or not) for it to
display correctly. That is a GTK detail, not an issue with Waybar. See the
*STYLE* section below.
*Warning*: If *min* is set to *0* (default), the slider can set brightness to
*0*, which may completely disable the backlight on some devices, making the
screen fully black. Consider setting a small minimum value (e.g. *10*) or
@@ -15,6 +15,11 @@ The *backlight* module displays the current backlight level.
default: 2 ++
The interval in which information gets polled.
*device*: ++
typeof: string ++
The name of the preferred backlight device to display. If left empty, a device will be chosen automatically. ++
Both screen backlights (the udev *backlight* subsystem) and keyboard backlights (LEDs in the udev *leds* subsystem, e.g. *white:kbd_backlight*) are supported; name such an LED here to control it. When left empty, a screen backlight is always preferred for automatic selection.
*format*: ++
typeof: string ++
default: {percent}% ++
@@ -74,7 +79,13 @@ The *backlight* module displays the current backlight level.
*reverse-scrolling*: ++
typeof: bool ++
Option to reverse the scroll direction.
default: false ++
Option to reverse the scroll direction for devices other than a mouse (touchpad, trackpad, etc).
*reverse-mouse-scrolling*: ++
typeof: bool ++
default: false ++
Option to reverse the scroll direction for mice.
*scroll-step*: ++
typeof: float ++
@@ -86,6 +97,15 @@ The *backlight* module displays the current backlight level.
default: 0.0 ++
The minimum brightness of the backlight.
*tooltip*: ++
typeof: bool ++
default: true ++
Option to disable tooltip on hover.
*tooltip-format*: ++
typeof: string ++
Text to be displayed in the tooltip.
*menu*: ++
typeof: string ++
Action that popups the menu.
@@ -104,6 +124,16 @@ The *backlight* module displays the current backlight level.
default: false ++
Enables this module to consume all left over space dynamically.
#FORMAT REPLACEMENTS
*{percent}*: The current brightness in percent.
*{percent_exp}*: The current brightness in percent, adjusted with a power curve to better match perceived brightness.
*{icon}*: The icon from *format-icons* chosen according to *{percent}*.
*{icon_exp}*: The icon from *format-icons* chosen according to *{percent_exp}*.
@@ -27,11 +27,26 @@ The *battery* module displays the current capacity and state (eg. charging) of y
default: false ++
Option to use the battery design capacity instead of its current maximal capacity.
*full-at-plugged*: ++
typeof: bool ++
default: false ++
When enabled, a battery that is *Full* while the adapter is online is reported with the *Plugged* status instead of *Full* (so you can style/format it separately). Disabled by default to preserve the existing *Full* behaviour.
*interval*: ++
typeof: integer ++
default: 60 ++
The interval in which the information gets polled.
*smooth-power*: ++
typeof: bool ++
default: false ++
Enable exponential smoothing of the battery power reading used to estimate the *{time}* remaining, so the estimate fluctuates less between refreshes.
*smooth-power-time-constant*: ++
typeof: double ++
default: 260.0 ++
The time constant (in seconds) of the *smooth-power* exponential filter. Only used when *smooth-power* is enabled. Values below 1.0 are clamped to 1.0.
*states*: ++
typeof: object ++
A number of battery states which get activated on certain capacity levels. See *waybar-states(5)*.
@@ -109,6 +124,11 @@ The *battery* module displays the current capacity and state (eg. charging) of y
default: true ++
Option to disable tooltip on hover.
*tooltip-format*: ++
typeof: string ++
default: {timeTo} ++
The tooltip format. See *CUSTOM FORMATS* for status/state-specific variants.
*bat-compatibility*: ++
typeof: bool ++
default: false ++
@@ -150,6 +170,12 @@ The *battery* module displays the current capacity and state (eg. charging) of y
*{health}*: The percentage of the highest-capacity battery's original maximum charge it can still hold.
The following additional replacement is available in *tooltip-format* (and its
custom variants) only:
*{timeTo}*: Either an estimate of time until full or empty, or "Full", "Plugged"
or "Empty" depending on the current battery status.
#TIME FORMAT
The *battery* module allows you to define how time should be formatted via *format-time*.
@@ -169,6 +195,11 @@ The *battery* module allows one to define custom formats based on up to two fact
*format-<status>-<state>*: You can also set a custom format depending on both values.
The tooltip format can be customized the same way. The best fitting from
*tooltip-format*, *tooltip-format-<state>*, *tooltip-format-<status>* and
*tooltip-format-<status>-<state>* will be used (using the same logic as
*format-\**).
#STATES
- Every entry (*state*) consists of a *<name>* (typeof: *string*) and a *<value>* (typeof: *integer*).
@@ -214,6 +245,20 @@ Where:
}
```
Custom icon set depending on the status (*format-icons* as an object):
Use the controller with the defined alias. Otherwise, a random controller is used. Recommended to define when there is more than 1 controller available to the system.
*controller-alias*: ++
typeof: string ++
Synonym for *controller*. If both are set, *controller-alias* takes precedence.
*format-device-preference*: ++
typeof: array ++
A ranking of bluetooth devices, addressed by their alias. The order is from *first displayed* to *last displayed*. ++
@@ -178,6 +182,9 @@ At the time of writing, the experimental features of BlueZ need to be turned on,
*{device_battery_percentage}*: Battery percentage of the displayed device if available. Use only in the config options defined below.
*{device_battery_percentage_peripheral}*: Battery percentage of the peripheral half of a split keyboard (e.g., ZMK keyboards with separate central and peripheral batteries). ++
This is read from GATT Battery Service characteristics that have a User Description descriptor. Use only in the config options defined below.
## CONFIGURATION
*format-connected-battery*: ++
@@ -220,6 +227,17 @@ At the time of writing, the experimental features of BlueZ need to be turned on,
}
```
Split keyboard with separate central/peripheral batteries (e.g., ZMK):
:[ Audio source identifier. See the cava documentation for details.
|[ *sample_rate*
:[ long integer
:[ 44100
:[ See cava configuration
:[ See the cava documentation.
|[ *sample_bits*
:[ integer
:[ 16
:[ See cava configuration
:[ See the cava documentation.
|[ *stereo*
:[ bool
:[ true
:[ Visual channels
:[ Enable stereo visualization.
|[ *reverse*
:[ bool
:[ false
:[ Displays frequencies the other way around
:[ Reverse the bar order (highest frequencies on the left).
|[ *bar_delimiter*
:[ integer
:[ 0
:[ Each bar is separated by a delimiter. Use decimal value in ascii table(i.e. 59 = ";"). 0 means no delimiter
:[ Delimiter placed between bars in the raw output. Use a decimal ASCII value (e.g. 59 = ";"). 0 means no delimiter.
|[ *data_format*
:[ string
:[ ascii
:[ Raw data format. Can be 'binary' or 'ascii'. **Raw frontend only.**
|[ *raw_target*
:[ string
:[
:[ Raw output target. A fifo will be created if target does not exist. **Raw frontend only.**
|[ *monstercat*
:[ bool
:[ false
:[ Disables or enables the so-called "Monstercat smoothing" with or without "waves"
:[ Enable Monstercat smoothing.
|[ *waves*
:[ bool
:[ false
:[ Disables or enables the so-called "Monstercat smoothing" with or without "waves"
:[ Enable the waves effect alongside Monstercat smoothing.
|[ *noise_reduction*
:[ integer
:[ 77
:[ Range between 0 - 100. The raw visualization is very noisy, this factor adjusts the integral and gravity filters to keep the signal smooth. 100 will be very slow and smooth, 0 will be fast but noisy
:[ double
:[ 0.77
:[ Smoothing factor between 0.0 and 1.0. Higher values produce slower, smoother animation; lower values are more reactive but noisy.
|[ *input_delay*
:[ integer
:[ 2
:[ Sets the delay before fetching audio source thread start working. On author's machine, Waybar starts much faster than pipewire audio server, and without a little delay cava module fails because pipewire is not ready
|[ *ascii_max_range*
:[ integer
:[ 7
:[ It's impossible to set it directly. The value is dictated by the number of icons in the array *format-icons*
|[ *data_format*
:[ string
:[ asci
:[ Raw data format. Can be 'binary' or 'ascii'
|[ *raw_target*
:[ string
:[ /dev/stdout
:[ Raw output target. A fifo will be created if target does not exist
|[ *menu*
:[ string
:[
:[ Action that popups the menu.
|[ *menu-file*
:[ string
:[
:[ Location of the menu descriptor file. There need to be an element of type GtkMenu with id *menu*
|[ *menu-actions*
:[ array
:[
:[ The actions corresponding to the buttons of the menu.
|[ *bar_spacing*
:[ integer
:[
:[ Bars' space between bars in number of characters
:[ 4
:[ Delay in seconds before starting audio capture. Increase this if Waybar starts before the audio server (e.g. PipeWire).
|[ *bar_width*
:[ integer
:[
:[ Bars' width between bars in number of characters
:[ Bar width in pixels. **Used by the GLSL frontend.**
|[ *bar_spacing*
:[ integer
:[
:[ Space between bars in pixels. **Used by the GLSL frontend.**
|[ *bar_height*
:[ integer
:[
:[ Useless. bar_height is only used for output in "noritake" format
|[ *background*
:[ Ignored by Waybar. Used only by cava's "noritake" output format.
|[ *menu*
:[ string
:[
:[ GLSL actual. Support hex code colors only. Must be within ''
|[ *foreground*
:[ Action that opens the menu.
|[ *menu-file*
:[ string
:[
:[ GLSL actual. Support hex code colors only. Must be within ''
|[ *gradient*
:[ integer
:[ 0
:[ GLSL actual. Gradient mode(0/1 - on/off)
|[ *gradient_count*
:[ integer
:[ 0
:[ GLSL actual. The count of colors for the gradient
|[ *gradient_color_N*
:[ string
:[ Location of the menu descriptor file. There must be a GtkMenu element with id *menu*.
|[ *menu-actions*
:[ array
:[
:[ GLSL actual. N - the number of the gradient color between 1 and 8. Only hex defined colors are supported. Must be within ''
:[ Actions corresponding to the buttons of the menu.
|[ *method* \[output\]
:[ string
:[ raw
:[ Cava output method. Set to *raw* for the text frontend or *sdl_glsl* for the GPU frontend. **This is set inside the *[output]* section of the cava configuration file, not in Waybar's JSON.**
|[ *sdl_width*
:[ integer
:[
:[ GLSL actual. Manages the width of the waybar cava GLSL frontend module
:[ GLSL frontend width in pixels. **GLSL only.**
|[ *sdl_height*
:[ integer
:[
:[ GLSL actual. Manages the height of the waybar cava GLSL frontend module
:[ GLSL frontend height in pixels. **GLSL only.**
|[ *min-length*
:[ integer
:[
:[ Requested width of the GLSL widget in pixels. If not set, falls back to *max-length*, then *sdl_width*. **GLSL only.**
|[ *max-length*
:[ integer
:[
:[ Fallback width of the GLSL widget if *min-length* is not set. **GLSL only.**
|[ *vertex_shader*
:[ string
:[
:[ Path to the vertex shader. **GLSL only; set in the *[output]* section of the cava configuration file.**
|[ *fragment_shader*
:[ string
:[
:[ Path to the fragment shader. **GLSL only; set in the *[output]* section of the cava configuration file.**
|[ *continuous_rendering*
:[ integer
:[ 0
:[ GLSL actual. Keep rendering even if no audio. Recommended to set to 1
:[ Continue rendering when silent. Set to 1 for smooth animation. **GLSL only; set in the *[output]* section of the cava configuration file.**
|[ *background*
:[ string
:[
:[ Background color as a '#RRGGBB' hex string (must be quoted). **GLSL only; set in the *[color]* section of the cava configuration file.**
|[ *foreground*
:[ string
:[
:[ Foreground color as a '#RRGGBB' hex string (must be quoted). **GLSL only; set in the *[color]* section of the cava configuration file.**
|[ *gradient*
:[ integer
:[ 0
:[ Enable gradient mode (0 = off, 1 = on). **GLSL only.** Can also be set in the *[color]* section of the cava configuration file.
|[ *gradient_count*
:[ integer
:[ 0
:[ Number of gradient colors (up to 8). **GLSL only.** Can also be set in the *[color]* section of the cava configuration file.
|[ *gradient_color_N*
:[ string
:[
:[ Gradient color N (1–8) as a '#RRGGBB' hex string (must be quoted). **GLSL only; set in the *[color]* section of the cava configuration file.**
Configuration can be provided as:
- The only cava configuration file which is provided through *cava_config*. The rest configuration can be skipped
- Without cava configuration file. In such case cava should be configured through provided list of the configuration option
- Mix. When provided both And cavaconfiguration file And configuration options. In such case, waybar applies configuration file first and then overrides particular options by the provided list of configuration options
Configuration can be provided in three ways:
- **Cava config only**: set *cava_config* to a cava configuration file and omit all other options.
- **Waybar JSON only**: leave out *cava_config* and set every option in Waybar's module configuration.
- **Mixed**: provide a *cava_config* and also set specific options in Waybar's JSON. Waybar reads the file first, then overrides any values present in the JSON.
#ACTIONS
[- *String*
:- *Action*
:[ *Action*
|[ *mode*
:< Switch main cava thread and fetch audio source thread from/to pause/resume
:[ Toggle pause/resume for the audio capture and output threads.
#DEPENDENCIES
@@ -207,26 +224,32 @@ Configuration can be provided as:
#SOLVING ISSUES
. On start Waybar throws an exception "error while loading shared libraries: libcava.so: cannot open shared object file: No such file or directory".
It might happen when libcava for some reason hasn't been registered in the system. sudo ldconfig should help
. Waybar is starting but cava module doesn't react to the music
1. In such cases at first need to make sure usual cava application is working as well
2. If so, need to comment all configuration options. Uncomment cava_config and provide the path to the working cava config
3. You might set too huge or too small input_delay. Try to setup to 4 seconds, restart waybar, and check again 4 seconds past. Usual even on weak machines it should be enough
4. You might accidentally switch action mode to pause mode
. At startup Waybar fails with *"error while loading shared libraries: libcava.so: cannot open shared object file: No such file or directory"*.
This happens when libcava has not been registered in the system library cache. Run *sudo ldconfig* to refresh the cache.
This can also occur when Waybar is installed under */usr/local* but libcava is elsewhere. To fix it:
1. Remove the local libcava installation: *sudo rm -rfv /usr/local/include/cava /usr/local/lib64/pkgconfig/cava.pc /usr/local/lib64/libcava.so*
2. Reconfigure Waybar to use the system prefix: *meson configure build -Dprefix="/usr"*
3. Rebuild Waybar: *ninja -C build*
4. Install Waybar: *sudo meson install -C build*
#RISING ISSUES
. Waybar starts but the cava module does not react to audio.
1. First, verify that standalone cava works correctly.
2. If it does, comment out all Waybar cava options, uncomment *cava_config*, and point it to the working cava configuration file.
3. The *input_delay* may be too large or too small. Try setting it to 4 seconds, restart Waybar, and check again after that delay. This is usually sufficient, even on slower machines.
4. You may have accidentally toggled pause mode via an action.
For clear understanding: this module is a cava API's consumer. So for any bugs related to cava engine you should contact Cava upstream(https://github.com/karlstav/cava) ++
with the one Exception. Cava upstream doesn't provide cava as a shared library. For that, this module author made a fork libcava(https://github.com/LukashonakV/cava). ++
So the order is:
. cava upstream
. libcava upstream.
In case when cava releases new version and you're wanna get it, it should be raised an issue to libcava(https://github.com/LukashonakV/cava) with title ++
\[Bump\]x.x.x where x.x.x is cava release version.
#REPORTING ISSUES
This module is a consumer of the cava API. For bugs in the cava engine itself, please report them to [cava upstream](https://github.com/karlstav/cava) first.
Upstream cava does not provide a shared library. The Waybar cava module uses [libcava](https://github.com/LukashonakV/cava), a fork maintained by the module author, to provide one. If the issue is specific to the shared library packaging, report it to libcava.
When requesting a new upstream cava release to be packaged in libcava, open an issue at libcava with the title `[Bump] x.x.x`, where `x.x.x` is the desired cava version.
@@ -240,7 +263,6 @@ In case when cava releases new version and you're wanna get it, it should be rai
"source": "auto",
"stereo": true,
"reverse": false,
"bar_delimiter": 0,
"monstercat": false,
"waves": false,
"noise_reduction": 0.77,
@@ -251,36 +273,6 @@ In case when cava releases new version and you're wanna get it, it should be rai
}
},
```
#STYLE
- *#cava*
- *#cava.silent* Applied after no sound has been detected for sleep_timer seconds
- *#cava.updated* Applied when a new frame is shown
#FRONTENDS
## RAW
The cava raw frontend uses ASCII characters to visualize incoming audio data. Each ASCII symbol position corresponds to the value of the audio power pulse.
Under the hood:
```
. Incoming audio power pulse list is : 12684
. Configured array of ASCII codes is: ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█" ]. See `format-icons` https://github.com/Alexays/Waybar/wiki/Module:-Cava#example
# README.md contains further information on how to setup CAVA for JACK.
#
# The options 'sample_rate', 'sample_bits', 'channels' and 'autoconnect' can be configured for some input methods:
# sample_rate: fifo, pipewire, sndio, oss
# sample_bits: fifo, pipewire, sndio, oss
@@ -452,7 +445,7 @@ bar_delimiter = 0
# Noise reduction, int 0 - 100. default 77
# the raw visualization is very noisy, this factor adjusts the integral and gravity filters to keep the signal smooth
# 100 will be very slow and smooth, 0 will be fast but noisy.
# 100 will be very slow and smooth, 0 will be fast and noisy.
[eq]
@@ -461,33 +454,32 @@ bar_delimiter = 0
# Remember to uncomment more than one key! More keys = more precision.
# Look at readme.md on github for further explanations and examples.
```
## GLSL
The Cava GLSL frontend delegates the visualization of incoming audio data to the GPU via OpenGL.
There are some mandatory dependencies that need to be satisfied in order for Cava GLSL to be built and function properly:
## GLSL frontend
. epoxy library must be installed on the system
. Vertex and fragment shaders from the original project must be used. They should be downloaded, and the file paths must be configured correctly in the Waybar Cava configuration:
. It is highly recommended to have a separate cava configuration for the Waybar Cava GLSL module and to use this as the cava_config in the Waybar configuration.
. It is common for cava configurations to be placed in the XDG_CONFIG_HOME directory, including shaders as well. Consider keeping them in the $XDG_CONFIG_HOME/cava/shaders folder.
The GLSL frontend requires:
Key configuration options:
. The *epoxy* library.
. Vertex and fragment shaders from the cava project. Download them and place under _$XDG_CONFIG_HOME/cava/shaders_, then reference them in the cava configuration:
; for glsl output mode, keep rendering even if no audio
continuous_rendering = 1;
continuous_rendering = 1
# disable console blank (screen saver) in tty
# (Not supported on FreeBSD)
@@ -705,4 +697,21 @@ gradient_color_2 = '#45475A'
# Look at readme.md on github for further explanations and examples.
```
Different waybar_cava#N.conf see at [cava GLSL](https://github.com/Alexays/Waybar/wiki/Module:-Cava:-GLSL)
More GLSL examples are available on the [cava GLSL wiki page](https://github.com/Alexays/Waybar/wiki/Module:-Cava:-GLSL).
#STYLE
- *#cava* Raw frontend widget
- *#cava.silent* Applied after no sound has been detected for *sleep_timer* seconds
- *#cava.updated* Applied when a new frame is shown
- *#cavaGLSL* GLSL frontend widget (used instead of *#cava* when the cava *method* is *sdl_glsl*)
- *#cavaGLSL.silent* Applied after no sound has been detected for *sleep_timer* seconds
- *#cavaGLSL.updated* Applied when a new frame is shown
#FRONTENDS
## RAW
The raw frontend maps each bar's amplitude to a character from *format-icons*. The final widget text is the concatenation of all characters, optionally separated by *bar_delimiter*. See the EXAMPLES section above for a complete configuration.
## GLSL
The GLSL frontend renders the visualization with OpenGL ES using user-provided shaders. It is selected by setting *method = sdl_glsl* in the cava configuration. See the EXAMPLES section above for a complete configuration.
@@ -21,6 +21,10 @@ Addressed by *hyprland/language*
typeof: string++
Provide an alternative name to display per language where <lang> is the language of your choosing. Can be passed multiple times with multiple languages as shown by the example below.
*format-<lang>-<variant>*++
typeof: string ++
Like *format-<lang>* but also matches the layout variant, taking precedence over *format-<lang>* when both the language and variant match.
*keyboard-name*: ++
typeof: string ++
Specifies which keyboard to use from hyprctl devices output. Using the option that begins with "at-translated-set..." is recommended.
@@ -38,6 +42,24 @@ Addressed by *hyprland/language*
typeof: array ++
The actions corresponding to the buttons of the menu.
*tooltip*: ++
typeof: boolean ++
default: true ++
Enables or disables the tooltip for the language module. By default, the tooltip is enabled. Set to *false* to disable.
*tooltip-format*: ++
typeof: string ++
default: {long} ++
Specifies the format of the tooltip when it is enabled. It follows the same format replacement rules as the *format* key.
*tooltip-format-<lang>*: ++
typeof: string ++
Allows specifying a different tooltip format for each language. The *<lang>* should be replaced with the language code. This can be used to provide a custom tooltip for each language.
*tooltip-format-<lang>-<variant>*: ++
typeof: string ++
Like *tooltip-format-<lang>* but also matches the layout variant, taking precedence over *tooltip-format-<lang>* when both the language and variant match.
*expand*: ++
typeof: bool ++
default: false ++
@@ -46,7 +68,7 @@ Addressed by *hyprland/language*
#FORMAT REPLACEMENTS
*{short}*: Short name of layout (e.g. "us"). Equals to {}.
*{short}*: Short name of layout (e.g. "us").
*{shortDescription}*: Short description of layout (e.g. "en").
@@ -66,6 +88,29 @@ Addressed by *hyprland/language*
}
```
```
"hyprland/language": {
"format": "{}",
"format-en": "US",
"format-es": "ES",
"tooltip": true,
"tooltip-format": "{long}"
}
```
```
"hyprland/language": {
"format": "{}",
"format-en": "US",
"format-es": "ES",
"tooltip": true,
"tooltip-format": "{}",
"tooltip-format-es": "{Español}",
"tooltip-format-en": "{English (american)}"
}
```
#STYLE
- *#language*
- *#language.<layout>* (per-layout class derived from the layout short name, e.g. '.us')
The format, how information should be displayed. On {} the currently active submap is displayed.
The format, how information should be displayed. This format supports two placeholders: *{submap}* for the currently active submap name and *{icon}* for the icon associated with the submap.
*rotate*: ++
typeof: integer ++
@@ -80,6 +80,10 @@ Addressed by *hyprland/submap*
default: Default ++
Option to set the submap name to display when not in an active submap.
*icons*: ++
typeof: object ++
Based on the submap name, the corresponding icon will be selected from this map and made available as the *{icon}* placeholder in the format string. The keys are submap names, and the values are the icons or strings to display for those submaps.
*menu*: ++
typeof: string ++
Action that popups the menu.
@@ -103,9 +107,13 @@ Addressed by *hyprland/submap*
The format, how information should be displayed. On {} the current window title is displayed.
*max-length*: ++
typeof: integer ++
The maximum length in character the module should display.
*rewrite*: ++
typeof: object ++
Rules to rewrite window title. See *rewrite rules*.
@@ -25,6 +29,15 @@ Addressed by *hyprland/window*
typeof: bool ++
Show the active window of the monitor the bar belongs to, instead of the focused window.
*fallback*: ++
typeof: string ++
Text to display when the focused window title is empty (for example when no window is focused).
*tooltip-format*: ++
typeof: string ++
The format of the tooltip shown on hover. Supports the same replacements as *format*. Requires *tooltip* to be enabled. When unset, the tooltip falls back to the formatted label text. ++
@@ -14,11 +14,11 @@ Addressed by *hyprland/workspaces*
*format*: ++
typeof: string ++
default: {id} ++
default: {name} ++
The format, how information should be displayed.
*format-icons*: ++
typeof: array ++
typeof: object ++
Based on the workspace ID and state, the corresponding icon gets selected. See *icons*.
*window-rewrite*: ++
@@ -41,6 +41,19 @@ This setting is ignored if *workspace-taskbar.enable* is set to true.
The separator to be used between windows in a workspace. ++
This setting is ignored if *workspace-taskbar.enable* is set to true.
*window-rewrite-group-threshold*: ++
typeof: int ++
default: 0 ++
When a workspace contains at least this many windows with the same rewrite result, they are collapsed into a single one using *window-rewrite-group-format*. ++
Set to 0 to disable grouping. ++
This setting is ignored if *workspace-taskbar.enable* is set to true.
*window-rewrite-group-format*: ++
typeof: string ++
default: "{icon}×{count}" ++
The format used to represent a group of collapsed windows. Available placeholders are {icon} (the icon being grouped) and {count} (how many windows share it). ++
This setting is ignored if *workspace-taskbar.enable* is set to true.
*workspace-taskbar*: ++
typeof: object ++
Contains settings for the workspace taskbar, an alternative mode for the workspaces module which displays the window icons as images instead of text.
@@ -75,6 +88,11 @@ This setting is ignored if *workspace-taskbar.enable* is set to true.
default: 16 ++
Size of the icons in the workspace taskbar.
*max-icons*: ++
typeof: int ++
default: 0 (unlimited) ++
Maximum number of icons to show per workspace. When set, duplicate icons (windows with the same class) are removed first, then the list is trimmed to this limit. Set to 0 for unlimited icons.
*icon-theme*: ++
typeof: string | array ++
default: [] ++
@@ -98,6 +116,11 @@ This setting is ignored if *workspace-taskbar.enable* is set to true.
- {button} Pressed button number, see https://api.gtkd.org/gdk.c.types.GdkEventButton.button.html. ++
See https://github.com/Alexays/Waybar/wiki/Module:-Hyprland#workspace-taskbars-example for a full example.
*max-windows*: ++
typeof: int ++
default: 0 (unlimited) ++
Maximum number of windows to show per workspace. When set, newest windows beyond the limit are not shown. Set to 0 for unlimited windows.
*show-special*: ++
typeof: bool ++
default: false ++
@@ -113,6 +136,12 @@ This setting is ignored if *workspace-taskbar.enable* is set to true.
default: false ++
If set to true, only persistent workspaces will be shown on bar.
*persistent-workspaces*: ++
typeof: object ++
default: empty ++
Lists workspaces that should always be shown, even when they do not exist. Keys are workspace names and values are arrays of output names on which the workspace should be shown (an empty array means all outputs). See the examples below. ++
Note: for persistent workspaces to actually work you must also declare them in your Hyprland config, e.g. *workspace = 1, monitor:eDP-1, persistent:true*.
*all-outputs*: ++
typeof: bool ++
default: false ++
@@ -123,6 +152,11 @@ This setting is ignored if *workspace-taskbar.enable* is set to true.
default: false ++
If set to true, only the active workspace will be shown.
*hide-active*: ++
typeof: bool ++
default: false ++
If set to true, the active workspace will be hidden. Unless a workspace is persistent or special.
*move-to-monitor*: ++
typeof: bool ++
default: false ++
@@ -130,11 +164,24 @@ This setting is ignored if *workspace-taskbar.enable* is set to true.
Otherwise, the workspace will open on the monitor where it was previously assigned.
Analog to using `focusworkspaceoncurrentmonitor` dispatcher instead of `workspace` in Hyprland.
*unique-icons*: ++
typeof: bool ++
default: false ++
If set to true, only one instance of each window icon will be shown per workspace.
*enable-bar-scroll*: ++
typeof: bool ++
default: false ++
If set to false, you can't scroll to cycle throughout workspaces from the entire bar. If set to true this behaviour is enabled.
*on-scroll-up*: ++
typeof: string ++
Command to execute when scrolling up on the module. This replaces the default behaviour of workspace cycling.
*on-scroll-down*: ++
typeof: string ++
Command to execute when scrolling down on the module. This replaces the default behaviour of workspace cycling.
*ignore-workspaces*: ++
typeof: array ++
default: [] ++
@@ -149,6 +196,15 @@ This setting is ignored if *workspace-taskbar.enable* is set to true.
If set to special-centered, workspaces will sort by default with special workspaces in the center.
If none of those, workspaces will sort with default behavior.
*tooltip*: ++
typeof: bool ++
default: true ++
Option to disable tooltip on hover.
*tooltips*: ++
typeof: object ++
Based on the workspace ID and state, the corresponding tooltip gets selected. Selection works the same as *format-icons* do. Format replacements are supported. See *icons*.
*expand*: ++
typeof: bool ++
default: false ++
@@ -162,6 +218,34 @@ This setting is ignored if *workspace-taskbar.enable* is set to true.
*{icon}*: Icon, as defined in *format-icons*.
*{windows}*: The windows in the workspace, formatted according to *window-rewrite* and joined with *format-window-separator*.
#WINDOW REWRITE RULES
The rules in *window-rewrite* are regexes that may match against a window's
class, title, or both. There are four categories of rule, distinguished by how
they are written in the config:
[- *Rule*
:- *Category*
|[ *something*
:[ Vague
|[ *class<something>*
:[ Class-only
|[ *title<something>*
:[ Title-only
|[ *class<something1> title<something2>*
:[ Hybrid
When the config contains only "vague" rules, they are matched against window
*classes* only. This is both for backwards compatibility and for performance:
matching against the title requires listening to window title changes via
Hyprland's IPC, which is unnecessary when no title rule is in use.
When the config contains *at least one* "title-only" or "hybrid" rule, then all
"vague" rules match against *both* class and title. This lets you define vague
rules where it does not matter whether the class or the title matched.
#ICONS
Additional to workspace name matching, the following *format-icons* can be set.
@@ -172,6 +256,7 @@ Additional to workspace name matching, the following *format-icons* can be set.
- *empty*: Will be shown on non-active, non-special empty persistent workspaces
- *visible*: Will be shown on workspaces that are visible but not active. For example: this is useful if you want your visible workspaces on other monitors to have the same look as active.
- *persistent*: Will be shown on non-empty persistent workspaces
- *urgent*: Will be shown on non-active urgent workspaces
#EXAMPLES
@@ -238,6 +323,51 @@ Additional to workspace name matching, the following *format-icons* can be set.
}
```
```
"hyprland/workspaces": {
"format": "{icon}",
"format-window-separator": ", ",
"tooltip": true,
"tooltips": {
"default": "{name}: {windows}",
"empty": "" // Will result in no tooltip
}
"format-icons": {
"1": "",
"2": "",
"3": "",
"4": "",
"5": "",
"active": "",
"default": ""
},
// Window rewrites omitted for brevity
}
```
```
"hyprland/workspaces": {
"format": "{icon}",
"format-window-separator": ", ",
"tooltip": true,
"tooltips": {
"1": "This is the first workspace",
"2": "This is the second",
"2": "And this is the third"
}
"format-icons": {
"1": "",
"2": "",
"3": "",
"4": "",
"5": "",
"active": "",
"default": ""
},
// Window rewrites omitted for brevity
}
```
#Style
- *#workspaces*
@@ -248,6 +378,8 @@ Additional to workspace name matching, the following *format-icons* can be set.
- *#workspaces button.persistent*
- *#workspaces button.special*
- *#workspaces button.urgent*
- *#workspaces button.workspace-hover* (applied while the pointer is over the button)
- *#workspaces button.<workspace-name>* (per-workspace class derived from the workspace name, sanitized to a valid CSS class name, e.g. a workspace named "1" yields '.ws-1'; special workspaces also get the raw name class)
- *#workspaces button.hosting-monitor* (gets applied if workspace-monitor == waybar-monitor)
- *#workspaces .workspace-label*
- *#workspaces .taskbar-window* (each window in the taskbar, only if 'workspace-taskbar.enable' is true)
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.