Commit Graph
5247 Commits
Author SHA1 Message Date
Alex 74cf45d530 fix(hyprland): detect Lua protocol without side-effecting dispatch
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.
2026-07-05 10:13:24 +02:00
Alex 30dcd7a7ca fix(hyprland/workspaces): own debounce timer on main thread, fix UAF
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.
2026-07-05 10:13:24 +02:00
Alex 4764a62afc fix(wwan): null members after unref and guard destructor to avoid double-free
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.
2026-07-05 10:13:24 +02:00
Alex c16e7efa13 fix(niri): close the IPC socket fd once (ScopedFd owns it), not twice
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.
2026-07-05 10:13:24 +02:00
Alex 77734e9b02 custom-graph: fix worker crash, JSON UTF-8 validation, SIGRTMIN guard
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.
2026-07-05 10:13:24 +02:00
Alex 48db4aa36e fix(bar): make disable-on-sleep DPMS suspend actually reach modules
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.
2026-07-05 10:13:24 +02:00
Alex afa6ab1fd8 fix(AModule): don't crash on click/scroll commands with literal braces
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.
2026-07-05 10:13:24 +02:00
Alex fc297a7df3 ci: only trigger on push to master to avoid duplicate PR runs
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.
2026-07-05 10:10:04 +02:00
Alex acc7060be6 fix(wireplumber): guard async callbacks by connection generation; wire scroll once
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.
2026-07-05 09:53:11 +02:00
Alex 0729823c13 fix(image): don't crash at startup when the widget isn't realized yet
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.
2026-07-05 09:47:47 +02:00
Alexis Rouillard 93cabeefc2 Merge pull request #5172 from khaneliman/fix/label-markup-cache-pua-collation
fix(label): compare markup cache by raw bytes, not collation
2026-07-05 09:40:47 +02:00
Austin Horstman a51bddab9e fix(label): compare markup cache by raw bytes, not collation
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.
2026-07-05 01:18:53 -05:00
Alexis Rouillard 57c1ac789f Merge pull request #5170 from layus/feat/pow-format-modifiers 2026-07-05 08:03:48 +02:00
Alexis Rouillard c63ece5c83 Merge pull request #5171 from khaneliman/fix/niri-ipc-initial-connect-regression 2026-07-05 08:01:24 +02:00
Austin Horstman 64f70caa46 fix(niri): keep the initial IPC connect synchronous
#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.
2026-07-05 00:29:27 -05:00
Guillaume Maudoux a3dc55f9ee feat(format): expand pow_format modifiers
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.
2026-07-04 23:08:42 +02:00
Alexis Rouillard 2d9b95e9de Merge pull request #4915 from khaneliman/custom-gio 2026-07-04 18:48:03 +02:00
Austin Horstman 1b6173ddda fix(util): keep GLib child setup fork-safe
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>
2026-07-04 08:41:35 -05:00
Austin Horstman f987c24144 style(custom): apply format and tidy cleanups
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>
2026-07-04 08:41:35 -05:00
Austin Horstman fc01c03a20 fix(custom): avoid blocking child reaps in interval worker
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>
2026-07-04 08:41:35 -05:00
Austin Horstman 5f4b96ad1d refactor(custom): move continuous exec onto GLib command stream
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>
2026-07-04 08:41:35 -05:00
Austin Horstman 560f02509b test(util): cover command stream line delivery and EOF flushing
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>
2026-07-04 08:40:31 -05:00
Austin Horstman 2355486cb3 feat(util): add GLib command stream for nonblocking line reads
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>
2026-07-04 08:40:31 -05:00
Alexis Rouillard 1471cb26b8 Merge pull request #5167 from Alexays/fix/bug-batch
fix: backlight leds subsystem (#2848), sway/ipc reconnect (#3166), ethernet unplug regression (#4364)
2026-07-04 13:53:28 +02:00
Alex faf6a62bcf fix(network): detect ethernet cable unplug again (carrier/operstate)
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.
2026-07-04 13:43:01 +02:00
Alex b0b46ec039 fix(sway/ipc): reconnect on disconnect instead of breaking + CPU-spinning
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.
2026-07-04 13:43:01 +02:00
Alex 8ff8ceeca2 fix(backlight): also match the leds subsystem for keyboard backlights
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.
2026-07-04 13:43:01 +02:00
Alexis Rouillard 4c55819888 Merge pull request #5166 from Alexays/ci/smoke-test
ci: headless smoke + screenshot test for the bar
2026-07-04 13:41:57 +02:00
Alex 0b88b4ef0c fix(wireplumber): reconnect when PipeWire/WirePlumber restarts
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.
2026-07-04 13:41:29 +02:00
Alex 0c059db0fd ci(smoke): add reference screenshot to enable golden comparison
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.
2026-07-04 13:28:48 +02:00
Alex e8638e761e ci(smoke): make custom modules emit text so the bar is not empty
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.
2026-07-04 13:22:08 +02:00
AlexandClaude Opus 4.8 cf2f120ad7 ci: add headless smoke + screenshot test for the bar
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>
2026-07-04 13:15:05 +02:00
Alexis Rouillard d9f2a437d0 Merge pull request #5165 from Alexays/fix/crash-batch3
fix: mpris resume SIGSEGV (#5124), wireplumber async UAF (#3974), stale reload batch state (#4129)
2026-07-04 13:12:17 +02:00
AlexandClaude Opus 4.8 54db6642ce docs: revamp README and add contributor docs
- 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>
2026-07-04 13:06:47 +02:00
Alexis Rouillard 6d3656a4a8 Merge pull request #5164 from Alexays/fix/tray-taskbar-mpd
fix: tray accel-group crash (#5142), wlr/taskbar shell-exec of built-in actions (#3284), MPD connect-timeout freeze (#1186)
2026-07-04 13:06:44 +02:00
Alex c19abf373b fix(mpris): defer widget visibility to update() to stop resume SIGSEGV
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.
2026-07-04 13:04:04 +02:00
Alex 8664d9a963 fix(wireplumber): guard async load callbacks against use-after-free on teardown
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.
2026-07-04 13:04:04 +02:00
Alex 268d859043 fix(reload): reset pending bar-batch state on rebind
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).
2026-07-04 13:04:02 +02:00
AlexandClaude Opus 4.8 5180dec43e ci(wiki): fix STYLE lists rendering as blockquotes
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>
2026-07-04 12:53:20 +02:00
Alexis Rouillard 0895465430 Merge pull request #5163 from Alexays/fix-2215
fix(keyboard-state): read lock LEDs from all devices, not just the first
2026-07-04 12:53:02 +02:00
Alex cb968c9369 fix(mpd): bound the connect timeout so an unreachable server can't freeze the bar
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.
2026-07-04 12:50:30 +02:00
Alex b7a6fa8c91 fix(wlr/taskbar): dispatch built-in click actions via doAction, not the shell
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.
2026-07-04 12:50:30 +02:00
Alex fae2b311de fix(tray): set an accel group on the dbusmenu client to stop Gtk-CRITICAL crash
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.
2026-07-04 12:50:30 +02:00
AlexandClaude Opus 4.8 33223d5534 ci(wiki): render option tables, pin pandoc, add visible auto-gen note
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>
2026-07-04 12:49:30 +02:00
Alex b2d15d1e3a fix(keyboard-state): read lock LEDs from all devices, not just the first
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.
2026-07-04 12:36:32 +02:00
Alexis Rouillard 8541ec8189 Merge pull request #5160 from Alexays/fix/clock-graceful
fix(clock): degrade gracefully on an invalid format specifier
2026-07-04 09:13:28 +02:00
Alexis Rouillard 368430208b Merge pull request #5161 from Alexays/fix-wireplumber
fix(wireplumber): correct linear→cubic volume conversion (0% on Bluetooth) + source-muted state
2026-07-04 09:13:11 +02:00
Alex 2650f062b5 fix(wireplumber): apply source-muted for Audio/Source modules
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.
2026-07-04 08:57:36 +02:00
Alex 69b9a14b96 fix(wireplumber): show correct volume on Bluetooth sinks
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.
2026-07-04 08:57:15 +02:00
Alex 23d63d2b84 fix(clock): degrade gracefully on an invalid format specifier
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.
2026-07-04 08:46:32 +02:00