Skip to content

Add capture_hidden option to filter peak monitoring for hidden items - #75

Closed
HoneyHazard wants to merge 9 commits into
tsowell:mainfrom
HoneyHazard:hide-items-capture-filter
Closed

Add capture_hidden option to filter peak monitoring for hidden items#75
HoneyHazard wants to merge 9 commits into
tsowell:mainfrom
HoneyHazard:hide-items-capture-filter

Conversation

@HoneyHazard

@HoneyHazard HoneyHazard commented Aug 9, 2026

Copy link
Copy Markdown

⚠️ Full Disclosure: Drafted with AI assistance (Claude); reviewed by me briefly. I am neither a RUST developer nor pipewire expert. If I should stop making these PRs into your wonderful project, please let me know. ⚠️

That being said, I hope these can be helpful and useful additions that users could appreciate.


Follow-up to the hide-items-permanent PR (#72, which itself follows #71) - stacked on top of that branch, not a standalone feature.

Hiding an item (per-instance with t, or permanently with Ctrl+t) currently always stops its peak capture unconditionally, with no way to opt out. That's a hardcoded behavior, not a configurable one - hiding is about decluttering the visible list, and some users may want hidden items to keep reporting levels exactly like any other item rather than having that tied to visibility.

On naming: t/Ctrl+t were chosen as a mnemonic for "toggle hide," not "toggle visibility" - deliberately, since this PR is exactly the case where hiding an item and suspending its monitoring are two separate, independently-toggleable things rather than one and the same. "Hide" names the primary, always-on effect (removed from the visible list); capture_hidden is what optionally layers a monitoring effect on top.

Adds capture_hidden: bool (default true), also available as --capture-hidden/--no-capture-hidden on the command line:

  • It's a filter layered on top of the existing capture-eligibility rules (lazy_capture, filters, etc.), never a replacement for them.
  • When true (the default), hidden items are captured under exactly the same rules as regular ones - reproducing today's non-hidden-item behavior for hidden items too, so nothing changes unless you opt in.
  • When false, hidden items are excluded from capture in addition to whatever else already excludes non-hidden items - matching the previous hardcoded-exclusion behavior exactly.
  • When monitoring actually is suspended for a hidden item (hidden + capture_hidden = false), the peak meter is left completely blank instead of drawing the usual inactive-looking placeholder over a stream that will never receive samples - that placeholder otherwise reads as broken rather than intentionally off.

Same hidden item side by side: peak meter still animating under the default capture_hidden=true, completely blank under --no-capture-hidden

Both instances show the same [hide] Lavf61.7.102: record item, hidden with t. Left: default capture_hidden = true - the meter keeps reporting real levels. Right: --no-capture-hidden - the meter is blank because the capture stream itself is stopped.

New config key (optional, no-op when unset):

capture_hidden = true

Also available as --capture-hidden/--no-capture-hidden on the command line.

Tested:

  • cargo test --release: 181/181 passing, including 5 new tests covering both capture_hidden = true/false for instance-hide, permanent-hide, fresh eligibility events, and the cross-instance file-sync path, plus a rendering test confirming the meter goes blank only when monitoring is actually suspended
  • cargo fmt --check / cargo clippy -- -D warnings / cargo doc (matching this repo's CI): all clean
  • Manually verified in tmux: toggling hide with capture_hidden left at its default keeps the peak meter animating; setting --no-capture-hidden blanks the meter for hidden items immediately and un-hiding resumes it

Drafted with AI assistance (Claude); reviewed by me before opening. Left as a draft while I finish going through it - not requesting review yet.

Lets a user hide individual nodes/devices from their own running
instance with the t key (toggle). Hidden state is purely local:
never persisted to disk, never synced to other running instances -
that's covered by a planned follow-up perma-hide tier (Ctrl+t).

- Hidden items sink to the bottom of their list, in a stable sort
  that otherwise preserves the existing object_serial order - both
  the visible group and the hidden group keep their relative order.
- Excluded from peak capture: start_capture() gates on
  hidden_instance the same way it already gates on lazy_capture's
  visible_objects, and toggling hidden explicitly stops/resumes an
  in-flight capture immediately rather than waiting for the next
  eligibility event.
- New row_hidden theme key, patched onto a hidden row's text spans
  (title, target, volume%, and Configuration tab's device/profile)
  using the same Style::patch() no-op-by-default mechanism as the
  rest of the theme system.
- New hidden_instance char_set key (default "[hide] ") prepended to
  a hidden item's title, so hidden items are identifiable even with
  row_hidden left unset - built-in char_sets and wiremix.toml's docs
  suggest an emoji alternative (e.g. "🙈 ").
Two fixes to the per-instance hide feature:

- row_hidden defaulted to an empty style in every built-in theme, so the
  only visual difference for a hidden row was its char_set prefix
  ("[hide] "/emoji) - easy to miss at a glance. default now dims it with
  DarkGray, nocolor with the DIM modifier (matching that theme's existing
  "dim = inactive" convention elsewhere). plain is left alone on purpose -
  it's styled nowhere else either, relying entirely on the prefix text.

- Hiding the selected item left the selection pinned to it. Since hidden
  items sink to the bottom of the list, this meant the cursor stayed on a
  now-relocated, visually de-emphasized row instead of following what the
  user was actually looking at. ObjectList::release_hidden_selection()
  moves the selection to whatever comes right after the hidden item in
  the list's current (pre-sink) order, falling back to whatever comes
  before it if it was last, or to no selection at all if it was the only
  item. Unhiding intentionally keeps the selection where it is - the user
  is still looking at that item, there's nothing to release.
Extends the per-instance hide from the previous branch with a second,
independent tier: Ctrl+t hides/shows the selected item durably -
saved to disk, and rediscovered against live nodes on every future
launch (including other already-running instances that reload the
same file, though nothing here makes that reload happen live yet -
see the follow-up branch for cross-instance sync via PipeWire
Metadata).

- Items are identified by node.name via a new MatchCondition::from_node_name()
  constructor, reusing the existing [[filters]] matching engine rather
  than raw object IDs, which don't survive a restart.
- New src/hidden_state.rs module handles load/save of a small
  $XDG_STATE_HOME/wiremix/hidden.toml file (mirroring the existing
  $XDG_CONFIG_HOME resolution for the main config file), with an
  atomic temp-file-then-rename write.
- Adds Serialize support to MatchCondition/MatchValue/PropertyKey
  (previously deserialize-only, since nothing needed to write a
  matcher back out before this), implemented as the exact inverse of
  their existing FromStr parsing.
- Sort order becomes three-tier: visible, then instance-hidden, then
  permanently-hidden at the very end - both hidden groups keep their
  own relative order.
- Reuses the row_hidden theme key from the previous branch (same
  faint-text treatment for both hidden tiers) and adds a second
  char_set key, hidden_permanent (default "[perm-hide] "), so the
  two tiers stay visually distinguishable by their title prefix even
  when styled identically.
- Capture gating gate is evaluated directly against the durable
  matcher list, not a cached per-object-ID set - the initial flood of
  capture-eligibility events on startup is handled before the first
  opportunity to recompute that cache, so relying on the cache alone
  let an already-permanently-hidden node start capturing again on
  every fresh launch (caught via live pw-dump verification, not the
  unit tests, which mock past the real startup ordering).
Watches the permanent-hide state file's directory for changes made by
other wiremix instances (e.g. another instance's own Ctrl+t), so the
change takes effect immediately everywhere instead of only on each
instance's next restart. Uses inotify on the directory rather than the
file itself, since HiddenState::save() writes via temp file + rename
and a direct file watch would go stale after the first rename. Linux-only,
gated behind target_os = "linux" with a no-op fallback elsewhere, matching
wiremix's existing de facto Linux-only scope (unconditional nix dependency,
no PipeWire port to other platforms).

This replaces an earlier PipeWire-Metadata-based approach for live sync
that was abandoned after a production incident; this design has no
PipeWire object creation/lifecycle involved at all, only local filesystem
event watching reusing the same directory HiddenState::save() already
writes to.
Same fix as the per-instance hide/show branch, applied to Ctrl+t:
hiding the selected item now moves the selection to whatever's next in
line (falling back to the previous item, or no selection at all if it
was the only one) instead of leaving it pinned to an item that's about
to sink to the bottom of the list. Reuses release_hidden_selection(),
already added for the instance-hide case. Un-hiding still keeps the
selection where it is.
Shorter and reads more clearly at a glance than "perm-hide" - "[hide]"
(instance) vs "[HIDE]" (permanent) distinguishes the two by case alone
rather than needing an extra word.
Hiding an item (per-instance or permanently) currently always stops
its peak capture unconditionally, with no way to opt out. That's a
hardcoded behavior, not a configurable one - and CPU/monitoring
concerns aside, some users may want hidden items to keep reporting
levels exactly like any other item, since hiding is about decluttering
the visible list, not necessarily about suppressing monitoring.

Adds capture_hidden: bool (default true), also available as
--capture-hidden/--no-capture-hidden on the command line. It's a
filter layered on top of the existing capture-eligibility rules
(lazy_capture, filters, etc.), never a replacement for them - when
true (the default), hidden items are captured under exactly the same
rules as regular ones, reproducing today's non-hidden-item behavior
for hidden items too. When false, hidden items are excluded from
capture in addition to whatever else already excludes non-hidden
items, matching the previous hardcoded-exclusion behavior exactly.

- start_capture()'s existing hidden_instance/hidden_permanent checks
  are now gated behind !capture_hidden instead of always applying.
- ToggleHiddenInstance/ToggleHiddenPermanent's explicit stop_capture()
  calls on hide (and the resuming start_capture() calls on unhide) are
  similarly gated - important not just for correctness under the new
  default, but to avoid double-starting an already-running capture
  stream when capture_hidden is true and hiding never stopped it in
  the first place.
- apply_file_hidden_state_change()'s stop sweep (triggered by another
  instance's hidden-state file changing) is gated the same way.
- When monitoring actually is suspended for a hidden item (hidden +
  capture_hidden = false), NodeWidget now leaves the peak meter
  completely blank instead of drawing the usual inactive-looking
  placeholder over a stream that will never receive samples, which
  otherwise reads as broken rather than intentionally off.

Stacked on top of the hide-items-permanent branch/PR - this is a
followup to that work, not a standalone feature.

Tested: cargo test --release (181/181 passing, including 5 new tests
covering both capture_hidden = true/false for instance-hide,
permanent-hide, fresh eligibility events, and the file-sync path, plus
a rendering test confirming the meter goes blank only when monitoring
is actually suspended), cargo fmt --check, cargo clippy -- -D
warnings, and cargo doc all clean.
@HoneyHazard
HoneyHazard force-pushed the hide-items-capture-filter branch from 3151f40 to 5e43606 Compare August 9, 2026 09:41
HoneyHazard added a commit to HoneyHazard/wiremix that referenced this pull request Aug 9, 2026
@HoneyHazard

Copy link
Copy Markdown
Author

Folding this into the stack it depends on instead of keeping it separate: capture_hidden now ships as part of #71 (scoped to the per-instance t hide), and #72 picks it up automatically via the branch stack and extends it to the permanent Ctrl+t tier and the cross-instance file-sync path. Closing this PR - no functional change from what was proposed here, just reorganized so the feature lands with the hide mechanism it belongs to rather than as an afterthought PR.

@HoneyHazard HoneyHazard closed this Aug 9, 2026
@HoneyHazard
HoneyHazard deleted the hide-items-capture-filter branch August 9, 2026 18:14
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.

1 participant