Skip to content

feat: integrate the Linux bell with the desktop environment, and coalesce BEL floods#1637

Open
nikicat wants to merge 2 commits into
raphamorim:mainfrom
nikicat:fix/bell-flood-coalesce
Open

feat: integrate the Linux bell with the desktop environment, and coalesce BEL floods#1637
nikicat wants to merge 2 commits into
raphamorim:mainfrom
nikicat:fix/bell-flood-coalesce

Conversation

@nikicat
Copy link
Copy Markdown

@nikicat nikicat commented Jun 2, 2026

This bundles two related bell changes. They are not strictly atomic, but the second builds directly on the first, so they are submitted together.

1. Integrate the Linux bell with the desktop environment (closes #1616)

On Linux the terminal bell now integrates with native DE attention mechanisms instead of only a synthesized 440 Hz tone:

  • [bell] audio accepts "system" (default on Linux) to play the freedesktop event-sound theme via libcanberra, respecting the user's sound theme, output routing, volume, mute and Do-Not-Disturb. libcanberra is loaded at runtime via dlopen, so there is no build-time dependency and it degrades gracefully when absent. true/false stay backwards-compatible (legacy tone / off).
  • [bell] urgency (default true on Linux) sets the window urgency / attention hint (X11 WM_HINTS, Wayland xdg-activation) when the bell fires in an unfocused window, and clears it on focus regain.
  • [bell] notification (default false) raises an org.freedesktop.Notifications notification for an unfocused window.

The audio/urgency/notification config values use named enums rather than bare bools. The cpal tone moves into a new bell module alongside the libcanberra binding. The new keys are documented in the rio.5 man page.

2. Coalesce BEL floods so a binary cat can't freeze the app

cat-ing a binary file streams thousands of BEL (0x07) bytes. Each one emitted a RioEvent::Bell, which flooded the event loop and — on Linux — ran the libcanberra/NSBeep call synchronously on the window thread per byte, freezing the whole app and ringing constantly until the source process was killed.

  • Coalesce in the backend bell(): emit at most one bell per bell_min_interval (new [bell] min-interval config, default 3000 ms). This is the only place that can stop the per-byte event flood before it reaches the event loop. The window is far longer than any bell sound, so it never re-triggers mid-playback without assuming the sound's length.
  • Move audio playback to a dedicated rio-bell worker thread, so the window thread is never blocked by audio I/O.

Tests

  • A regression test drives real BEL bytes through the parser into bell() and advances a deterministic mock clock (mock_instant, a dev-dependency only — production keeps std::time::Instant via a one-line cfg-gated alias) to prove the gate re-arms purely from elapsed time. It fails (50 000 bells) without the gate.
  • A second test asserts the gate is per-terminal, so one noisy terminal can never silence the others.

🤖 Generated with Claude Code

nikicat and others added 2 commits June 2, 2026 17:29
…im#1616)

On Linux the terminal bell now integrates with native DE attention
mechanisms instead of only a synthesized 440 Hz tone:

- `[bell] audio` accepts `"system"` (default on Linux) to play the
  freedesktop event-sound theme via libcanberra, respecting the user's
  sound theme, output routing, volume, mute and Do-Not-Disturb. It is
  loaded at runtime via dlopen, so there is no build-time dependency and
  it degrades gracefully when absent. `true`/`false` stay backwards
  compatible (legacy tone / off).
- `[bell] urgency` (default true on Linux) sets the window urgency /
  attention hint (X11 WM_HINTS, Wayland xdg-activation) when the bell
  fires in an unfocused window, and clears it on focus-regain.
- `[bell] notification` (default false) raises an
  org.freedesktop.Notifications notification for an unfocused window.

The audio/urgency/notification config values use named enums rather than
bare bools to avoid boolean blindness. The cpal tone moves into a new
`bell` module alongside the libcanberra binding. Documents the new keys
in the rio.5 man page.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`cat`-ing a binary file streams thousands of BEL (0x07) bytes. Each one
emitted a `RioEvent::Bell`, which flooded the event loop and — on Linux —
ran the libcanberra/NSBeep call synchronously on the window thread per
byte, freezing the whole app and ringing constantly until the source
process was killed.

Two independent fixes:

- Coalesce in the backend `bell()`: emit at most one bell per
  `bell_min_interval` (new `bell.min-interval` config, default 3s). This
  is the only place that can stop the per-byte event flood before it
  reaches the event loop. The window is far longer than any bell sound,
  so it never re-triggers mid-playback without assuming the sound length.
- Move audio playback to a dedicated `rio-bell` worker thread; the window
  thread is never touched.

Regression tests drive real BEL bytes through the parser into `bell()`
and advance a deterministic mock clock (mock_instant, dev-dependency
only; production keeps std::time::Instant via a cfg-gated alias) to prove
the gate re-arms purely from elapsed time. A second test asserts the gate
is per-terminal, so one noisy terminal can never silence the others.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request] Linux: integrate the bell with the desktop environment (window urgency hint / system sound theme) instead of a synthesized 440 Hz beep

1 participant