Commit Graph
5190 Commits
Author SHA1 Message Date
Alex 9a1cb6183a fix(ALabel): free g_strdup'd menu action string via closure notify
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.
2026-07-05 10:27:28 +02:00
Alex e8e12cdfa1 fix(audio_backend): make sink-mapping selection order-independent
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.
2026-07-05 10:27:28 +02:00
Alex b527376985 fix(audio_backend): release leaked pa_operation handles
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.
2026-07-05 10:27:28 +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
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 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
Alexis Rouillard ea33ceb055 Merge pull request #5158 from Alexays/fix/crash-freeze
fix: batch of crash/freeze fixes (clock DST, interval:0, thread-safety, reconnects)
2026-07-04 08:36:26 +02:00
Alex 45ab8a1211 fix(core): fall back to default interval for periodic modules on interval:0
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.
2026-07-04 08:35:11 +02:00
Alex 5c06233340 style: clang-format the crash-fix changes 2026-07-04 03:17:33 +02:00
Alex 6672e924df fix(niri): reconnect IPC and stop per-event throttling to prevent freeze
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.
2026-07-04 03:14:13 +02:00
Alex 3831524ba8 fix(audio_backend): never throw across the PulseAudio callback boundary
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.
2026-07-04 03:14:13 +02:00
Alex d3bfec13cc fix(keyboard-state): make device removal idempotent to stop libinput abort
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.
2026-07-04 03:14:13 +02:00
Alex 4229dc8ac2 fix(sway/window): resolve app icon on the main thread to stop icon-theme race
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.
2026-07-04 03:14:13 +02:00