Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ itertools = "0.14.0"
libspa = "0.9.2"
libspa-sys = "0.9.2"
log = "0.4.24"
nix = { version = "0.29.0", features = ["event", "term"] }
nix = { version = "0.29.0", features = ["event", "term", "inotify"] }
pipewire = { version = "0.9.2", features = ["v0_3_44"] }
pulp = "0.22.2"
ratatui = { version = "0.29.0", features = ["serde"] }
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ less-intuitive mouse controls are:
| ------------- | ----------------------- |
| q | Quit |
| m | Toggle mute |
| t | Hide/show (this instance only) |
| Ctrl+t | Hide/show (permanent, synced) |
| d | Set default source/sink |
| l/Right arrow | Increment volume |
| h/Left arrow | Decrement volume |
Expand Down
897 changes: 894 additions & 3 deletions src/app.rs

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub struct Config {
pub tab: usize,
pub tabs: Vec<TabKind>,
pub lazy_capture: bool,
pub capture_hidden: bool,
pub filters: Vec<MatchCondition>,
}

Expand Down Expand Up @@ -88,6 +89,8 @@ struct ConfigFile {
tabs: Vec<TabKind>,
#[serde(default = "default_lazy_capture")]
lazy_capture: bool,
#[serde(default = "default_capture_hidden")]
capture_hidden: bool,
#[serde(default = "Filter::defaults", deserialize_with = "Filter::merge")]
filters: Vec<Filter>,
}
Expand Down Expand Up @@ -145,6 +148,8 @@ pub struct NameOverride {
pub struct CharSet {
pub default_device: String,
pub default_stream: String,
pub hidden_instance: String,
pub hidden_permanent: String,
pub selector_top: String,
pub selector_middle: String,
pub selector_bottom: String,
Expand Down Expand Up @@ -193,6 +198,7 @@ pub struct Theme {
pub meter_center_active: Style,
pub config_device: Style,
pub config_profile: Style,
pub row_hidden: Style,
pub dropdown_icon: Style,
pub dropdown_border: Style,
pub dropdown_item: Style,
Expand Down Expand Up @@ -270,6 +276,10 @@ fn default_lazy_capture() -> bool {
false
}

fn default_capture_hidden() -> bool {
true
}

impl ConfigFile {
/// Override configuration with command-line arguments.
pub fn apply_opt(&mut self, opt: &Opt) {
Expand Down Expand Up @@ -328,6 +338,14 @@ impl ConfigFile {
if opt.lazy_capture {
self.lazy_capture = true;
}

if opt.no_capture_hidden {
self.capture_hidden = false;
}

if opt.capture_hidden {
self.capture_hidden = true;
}
}
}

Expand Down Expand Up @@ -396,6 +414,7 @@ impl TryFrom<ConfigFile> for Config {
tab,
tabs: config_file.tabs,
lazy_capture: config_file.lazy_capture,
capture_hidden: config_file.capture_hidden,
filters,
})
}
Expand Down Expand Up @@ -481,6 +500,7 @@ pub mod strict {
tab: Option<TabKind>,
tabs: Vec<TabKind>,
lazy_capture: bool,
capture_hidden: bool,
filters: Vec<Filter>,
}

Expand All @@ -502,6 +522,7 @@ pub mod strict {
tab: strict.tab,
tabs: strict.tabs,
lazy_capture: strict.lazy_capture,
capture_hidden: strict.capture_hidden,
filters: strict.filters,
}
}
Expand Down
10 changes: 10 additions & 0 deletions src/config/char_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ pub struct CharSetOverlay {
inherit: Option<String>,
default_device: Option<String>,
default_stream: Option<String>,
hidden_instance: Option<String>,
hidden_permanent: Option<String>,
selector_top: Option<String>,
selector_middle: Option<String>,
selector_bottom: Option<String>,
Expand Down Expand Up @@ -97,6 +99,8 @@ impl TryFrom<CharSetOverlay> for CharSet {

validate_and_set!(default_device, 1);
validate_and_set!(default_stream, 1);
validate_and_set!(hidden_instance, 0);
validate_and_set!(hidden_permanent, 0);
validate_and_set!(selector_top, 1);
validate_and_set!(selector_middle, 1);
validate_and_set!(selector_bottom, 1);
Expand Down Expand Up @@ -137,6 +141,8 @@ impl Default for CharSet {
Self {
default_device: String::from("◇"),
default_stream: String::from("◇"),
hidden_instance: String::from("[hide] "),
hidden_permanent: String::from("[HIDE] "),
selector_top: String::from("░"),
selector_middle: String::from("▒"),
selector_bottom: String::from("░"),
Expand Down Expand Up @@ -178,6 +184,8 @@ impl CharSet {
Self {
default_device: String::from("◊"),
default_stream: String::from("◊"),
hidden_instance: String::from("[hide] "),
hidden_permanent: String::from("[HIDE] "),
selector_top: String::from("░"),
selector_middle: String::from("▒"),
selector_bottom: String::from("░"),
Expand Down Expand Up @@ -209,6 +217,8 @@ impl CharSet {
Self {
default_device: String::from("*"),
default_stream: String::from("*"),
hidden_instance: String::from("[hide] "),
hidden_permanent: String::from("[HIDE] "),
selector_top: String::from("-"),
selector_middle: String::from("="),
selector_bottom: String::from("-"),
Expand Down
9 changes: 9 additions & 0 deletions src/config/keybinding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ impl Keybinding {
HashMap::from([
(event(KeyCode::Char('q')), Action::Exit),
(event(KeyCode::Char('m')), Action::ToggleMute),
// `t` for "toggle hide": toggles hiding the selected item for
// this instance only. Ctrl+t is the same "toggle hide" mnemonic,
// but permanently (saved to disk and synced to other running
// instances).
(event(KeyCode::Char('t')), Action::ToggleHiddenInstance),
(
KeyEvent::new(KeyCode::Char('t'), KeyModifiers::CONTROL),
Action::ToggleHiddenPermanent,
),
(event(KeyCode::Char('d')), Action::SetDefault),
(event(KeyCode::Char('l')), Action::SetRelativeVolume(0.01)),
(event(KeyCode::Right), Action::SetRelativeVolume(0.01)),
Expand Down
94 changes: 90 additions & 4 deletions src/config/matching.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use std::collections::HashMap;

use regex::Regex;
use serde::Deserialize;
use serde_with::DeserializeFromStr;
use serde::{Deserialize, Serialize};
use serde_with::{DeserializeFromStr, SerializeDisplay};

use crate::config::property_key::{PropertyKey, PropertyResolver};
use crate::wirehose::state;

#[derive(Debug, Deserialize)]
#[derive(Debug, Clone, Deserialize, Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct MatchCondition(pub HashMap<PropertyKey, MatchValue>);

Expand All @@ -21,9 +21,24 @@ impl MatchCondition {
.iter()
.all(|(key, value)| value.matches(resolver.resolve_key(state, key)))
}

/// Builds a matcher against a node's exact `node.name` - used to
/// durably identify a node for hide-items persistence. `node.name` is
/// the same property `Filter::defaults()` already relies on to
/// identify wiremix's own capture streams by name, the established
/// "stable enough" identifier for a node in this codebase. Not
/// perfectly stable for every kind of stream (an app that varies its
/// own `node.name` between launches won't re-match), the same
/// limitation any hand-written `[[filters]]` entry already has.
pub fn from_node_name(name: &str) -> Self {
Self(HashMap::from([(
PropertyKey::Bare(String::from("node.name")),
MatchValue::Literal(String::from(name)),
)]))
}
}

#[derive(Debug, DeserializeFromStr)]
#[derive(Debug, Clone, DeserializeFromStr, SerializeDisplay)]
pub enum MatchValue {
Literal(String),
NegatedLiteral(String),
Expand All @@ -33,6 +48,30 @@ pub enum MatchValue {
NotNull,
}

impl std::fmt::Display for MatchValue {
/// Inverse of `FromStr` - only round-trips values `FromStr` can
/// actually parse back: the literal string `"null"` is escaped as
/// `"null"` (quoted) so it isn't read back as the `Null` variant on
/// reload. Literal strings that themselves start with `!` or `~` are
/// not escaped, since `FromStr` has no quoting mechanism for that -
/// same pre-existing limitation as any hand-written `[[filters]]`
/// entry.
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MatchValue::Null => write!(f, "null"),
MatchValue::NotNull => write!(f, "!null"),
MatchValue::Literal(s) if s == "null" => write!(f, "\"null\""),
MatchValue::Literal(s) => write!(f, "{s}"),
MatchValue::NegatedLiteral(s) if s == "null" => {
write!(f, "!\"null\"")
}
MatchValue::NegatedLiteral(s) => write!(f, "!{s}"),
MatchValue::Regex(re) => write!(f, "~{}", re.as_str()),
MatchValue::NegatedRegex(re) => write!(f, "!~{}", re.as_str()),
}
}
}

#[cfg(test)]
impl PartialEq for MatchValue {
fn eq(&self, other: &Self) -> bool {
Expand Down Expand Up @@ -213,4 +252,51 @@ mod tests {
assert!(val.matches(Some("other")));
assert!(val.matches(None));
}

fn round_trip(s: &str) {
let val = s.parse::<MatchValue>().unwrap();
assert_eq!(val.to_string(), s);
let reparsed = val.to_string().parse::<MatchValue>().unwrap();
assert_eq!(val, reparsed);
}

#[test]
fn display_round_trips_null() {
round_trip("null");
}

#[test]
fn display_round_trips_not_null() {
round_trip("!null");
}

#[test]
fn display_round_trips_quoted_null_literal() {
round_trip("\"null\"");
}

#[test]
fn display_round_trips_negated_quoted_null_literal() {
round_trip("!\"null\"");
}

#[test]
fn display_round_trips_literal() {
round_trip("hello");
}

#[test]
fn display_round_trips_negated_literal() {
round_trip("!hello");
}

#[test]
fn display_round_trips_regex() {
round_trip("~^foo.*bar$");
}

#[test]
fn display_round_trips_negated_regex() {
round_trip("!~^foo.*bar$");
}
}
25 changes: 10 additions & 15 deletions src/config/property_key.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,26 @@
//! An identifier for a property on an object or a linked object

use serde_with::DeserializeFromStr;
use serde_with::{DeserializeFromStr, SerializeDisplay};

use crate::wirehose::state;

#[derive(Debug, Clone, Hash, PartialEq, Eq, DeserializeFromStr)]
#[derive(
Debug, Clone, Hash, PartialEq, Eq, DeserializeFromStr, SerializeDisplay,
)]
pub enum PropertyKey {
Device(String),
Node(String),
Client(String),
Bare(String),
}

#[allow(clippy::to_string_trait_impl)] // This is not for display.
impl ToString for PropertyKey {
fn to_string(&self) -> String {
impl std::fmt::Display for PropertyKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PropertyKey::Device(s) => {
format!("device:{s}")
}
PropertyKey::Node(s) => {
format!("node:{s}")
}
PropertyKey::Client(s) => {
format!("client:{s}")
}
PropertyKey::Bare(s) => s.to_string(),
PropertyKey::Device(s) => write!(f, "device:{s}"),
PropertyKey::Node(s) => write!(f, "node:{s}"),
PropertyKey::Client(s) => write!(f, "client:{s}"),
PropertyKey::Bare(s) => write!(f, "{s}"),
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/config/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub struct ThemeOverlay {
meter_center_active: Option<StyleDef>,
config_device: Option<StyleDef>,
config_profile: Option<StyleDef>,
row_hidden: Option<StyleDef>,
dropdown_icon: Option<StyleDef>,
dropdown_border: Option<StyleDef>,
dropdown_item: Option<StyleDef>,
Expand Down Expand Up @@ -108,6 +109,7 @@ impl TryFrom<ThemeOverlay> for Theme {
set!(meter_center_active);
set!(config_device);
set!(config_profile);
set!(row_hidden);
set!(dropdown_icon);
set!(dropdown_border);
set!(dropdown_item);
Expand Down Expand Up @@ -143,6 +145,7 @@ impl Default for Theme {
meter_center_active: Style::default().fg(Color::LightGreen),
config_device: Style::default(),
config_profile: Style::default(),
row_hidden: Style::default().fg(Color::DarkGray),
dropdown_icon: Style::default(),
dropdown_border: Style::default(),
dropdown_item: Style::default(),
Expand Down Expand Up @@ -187,6 +190,7 @@ impl Theme {
meter_center_active: Style::default().add_modifier(Modifier::BOLD),
config_device: Style::default(),
config_profile: Style::default(),
row_hidden: Style::default().add_modifier(Modifier::DIM),
dropdown_icon: Style::default(),
dropdown_border: Style::default(),
dropdown_item: Style::default(),
Expand Down Expand Up @@ -220,6 +224,7 @@ impl Theme {
meter_center_active: Style::default(),
config_device: Style::default(),
config_profile: Style::default(),
row_hidden: Style::default(),
dropdown_icon: Style::default(),
dropdown_border: Style::default(),
dropdown_item: Style::default(),
Expand Down
Loading
Loading