fix: further layer A (per-app) glitches

This commit is contained in:
atagen 2026-05-24 18:12:31 +10:00
parent 2978318019
commit 7797f60128
16 changed files with 1589 additions and 155 deletions

View file

@ -13,9 +13,9 @@ mod proto;
pub use codec::{Codec, DEFAULT_MAX_FRAME_BYTES, MIN_MAX_FRAME_BYTES};
pub use error::{Error, ErrorCode, ProtoError};
pub use proto::{
DaemonEvent, Event, HelloData, LayerALevel, MeterTick, Op, ProfileEvent, ProfileInfo, Request,
Response, ResponsePayload, Route, RouteList, RouteRule, RouteRuleMatch, RoutingEvent,
ServerFrame, SinkInfo, Sinks, Status, StreamRoute, Topic,
DaemonEvent, Event, HelloData, LayerALevel, LayerASnapshot, MeterTick, Op, ProfileEvent,
ProfileInfo, Request, Response, ResponsePayload, Route, RouteList, RouteRule, RouteRuleMatch,
RoutingEvent, ServerFrame, SinkInfo, Sinks, Status, StreamRoute, Topic,
};
/// Wire-protocol version. Bumped only on incompatible changes.

View file

@ -184,6 +184,36 @@ pub enum Op {
enabled: bool,
},
/// List per-app (Layer A) controller state for managed streams.
#[serde(rename = "per-app.list")]
LayerAList,
/// Enable or disable Layer A for a specific app (persistent
/// overlay override).
#[serde(rename = "per-app.set")]
PerAppSet {
/// Application identifier (process_binary or application_name).
app: String,
/// `true` to manage the app, `false` to leave it alone.
enabled: bool,
},
/// Enable or disable the Layer A master switch (persistent overlay
/// override).
#[serde(rename = "per-app.master")]
PerAppMaster {
/// `true` to enable Layer A globally.
enabled: bool,
},
/// Clear a managed stream's deference state (user-ceiling /
/// strict-mode lock) so the controller resumes normal control.
#[serde(rename = "per-app.reset")]
LayerAReset {
/// PipeWire node id of the managed stream.
node_id: u32,
},
/// Subscribe to one or more event topics on this connection.
#[serde(rename = "subscribe")]
Subscribe {
@ -217,6 +247,10 @@ impl Op {
Op::SettingSet { .. } => "setting.set",
Op::SettingList => "setting.list",
Op::BypassSet { .. } => "bypass.set",
Op::LayerAList => "per-app.list",
Op::PerAppSet { .. } => "per-app.set",
Op::PerAppMaster { .. } => "per-app.master",
Op::LayerAReset { .. } => "per-app.reset",
Op::Subscribe { .. } => "subscribe",
Op::Unsubscribe { .. } => "unsubscribe",
}
@ -356,10 +390,20 @@ pub struct Status {
pub profile: String,
/// Global bypass flag.
pub bypass: bool,
/// Layer A master switch (per-app level control enabled globally).
/// Older clients that don't understand the field treat it as
/// absent (serde `default`).
#[serde(default)]
pub per_app: bool,
/// Sink status snapshot.
pub sinks: Sinks,
/// Currently-tracked playback streams.
pub streams: Vec<StreamRoute>,
/// Per-app (Layer A) controller state for managed streams.
/// Empty when Layer A isn't managing anything. Older clients that
/// don't understand the field treat it as absent (serde `default`).
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub layer_a: Vec<LayerASnapshot>,
/// Non-fatal warnings the daemon wants operators to see —
/// typically from profile loading (TOML parse errors on a single
/// file, the active profile name pointing at something not on
@ -411,6 +455,29 @@ pub struct StreamRoute {
pub route: Route,
}
/// Per-app (Layer A) controller state for one managed stream.
/// Surfaced on `status` and `per-app.list`.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LayerASnapshot {
/// Source PipeWire node id.
pub node_id: u32,
/// Application identifier.
pub app: String,
/// True while a tap + controller is actively managing the stream.
pub managed: bool,
/// Last linear volume the controller wrote (1.0 = unity).
pub volume_lin: f32,
/// Smoothed gain reduction the controller currently asserts, in dB
/// (`>= 0`; `0` means no cut).
pub reduction_db: f32,
/// User-set ceiling (linear) when ceiling-mode deference is active.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub user_ceiling_lin: Option<f32>,
/// True when strict-mode deference has locked the controller until
/// an explicit `per-app.reset`.
pub deferred: bool,
}
/// Summary entry returned by `profile.list`.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ProfileInfo {