Skip to content

Add per-instance hide/show for list items (t key) - #71

Open
HoneyHazard wants to merge 4 commits into
tsowell:mainfrom
HoneyHazard:hide-items-instance
Open

Add per-instance hide/show for list items (t key)#71
HoneyHazard wants to merge 4 commits into
tsowell:mainfrom
HoneyHazard:hide-items-instance

Conversation

@HoneyHazard

@HoneyHazard HoneyHazard commented Aug 7, 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.


Sometimes a node in the list is just noise you'd rather not see or monitor for the rest of the session - a stream you know you're not going to touch, a device you don't care about right now. There was no way to get it out of the way without closing the app or reconfiguring [[filters]].

This adds a t keybinding that toggles hiding the selected item for the current instance only (t for "toggle hide" - the same mnemonic carries over to Ctrl+t in the follow-up permanent-hide PR, #72):

  • Hidden items sink to the bottom of their list, in a stable sort that otherwise preserves the existing order - both the still-visible group and the hidden group keep their relative order, so hiding something doesn't reshuffle everything else.
  • Hiding the selected item releases the selection rather than leaving it pinned to a row that's about to sink to the bottom - it moves to whatever's next in line, falling back to whatever's previous if it was the last item, or to no selection at all if it was the only item. Un-hiding keeps the selection where it is - you're still looking at that item, there's nothing to release.
  • Purely local to the running instance: nothing is written to disk, nothing is synced anywhere. Quitting and relaunching forgets it. (A durable, cross-instance-synced "permanent hide" tier is planned as a follow-up on Ctrl+t - this PR is deliberately just the local/session-only piece on its own.)

A visible item above a hidden one - dimmed text and a "[hide] " prefix on the title

Default theme, row_hidden at its new default (a faint DarkGray tint).

Two new config keys make the look of a hidden row customizable:

  • A new theme key, row_hidden: Style, patched onto a hidden row's text spans (title, target, volume%, and the Configuration tab's device/profile) - the same Style::patch() no-op-by-default mechanism the rest of the theme system already uses. Defaults to a faint DarkGray tint in the default theme and the DIM modifier in nocolor, so a hidden row is visually distinguishable out of the box, not just by its title prefix; plain leaves it unset on purpose, matching that theme's own "no styling anywhere" philosophy.
  • A new char_set key, hidden_instance: String (default "[hide] "), prepended to a hidden item's title - so a hidden item stays identifiable even if row_hidden is left unset. The built-in char_sets and wiremix.toml's docs suggest an emoji alternative ("🙈 ") for anyone who'd rather not use text.
[themes.default]
row_hidden = { fg = "DarkGray" }  # this is now the default - shown for clarity

[char_sets.default]
hidden_instance = "🙈 "

Peak monitoring for hidden items is a separate, configurable concern from visibility. Hiding an item always removes it from the visible list, but whether it also stops being monitored is controlled independently by a new capture_hidden: bool option (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 - hiding something never changes whether it's monitored unless you opt in.
  • When false, hidden items are excluded from capture in addition to whatever else already excludes non-hidden items.
  • 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.

# If false, hidden items are excluded from peak monitoring - an additional
# filter on top of lazy_capture and any other capture limits, not a
# replacement for them. When monitoring is excluded this way, the peak
# meter is left blank for that item rather than showing an inactive-looking
# placeholder. True by default, meaning hidden items follow the same
# monitoring rules as regular ones.
capture_hidden = true

Tested:

  • cargo test --release: 156/156 passing (includes new tests for: selection moving to the next item when hiding a middle row, to the previous item when hiding the last row, clearing entirely when hiding the only row, staying put when un-hiding, and capture_hidden in both directions for eligibility checks, explicit toggling, and the rendered meter)
  • cargo fmt --check / cargo clippy -- -D warnings / cargo doc (matching this repo's CI): all clean
  • Live-verified against a real PipeWire graph: hiding a node removes its wiremix-capture stream from the graph within the next frame (checked via pw-dump, matching capture nodes to their target by object.serial, not the node's own id); unhiding resumes it; toggling repeatedly leaves nothing orphaned
  • Live-verified the selection-release behavior and the default row_hidden styling in a real terminal
  • Verified leaving row_hidden unset in a custom theme (one that doesn't inherit = "default") reproduces stock text styling exactly, and that hiding/unhiding doesn't affect anything for users who never press t
  • 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

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.
Hiding an item for this instance 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.

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 check is now gated behind
  !capture_hidden instead of always applying.
- ToggleHiddenInstance's explicit stop_capture() call on hide (and the
  resuming start_capture() call 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.
- 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.
@HoneyHazard
HoneyHazard marked this pull request as ready for review August 10, 2026 05:16
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