---
task_id: souveraine-native-audio-001
title: "Souveraine native audio and privacy surfaces — PulseAudio for sound, PipeWire for portals"
status: in_progress
priority: high
phase: delivery
created: 2026-07-18
references:
  - surfaces/quickshell/services/Audio.qml
  - surfaces/quickshell/modules/ii/sidebarRight/volumeMixer/VolumeDialogContent.qml
  - surfaces/quickshell/modules/ii/bar/UtilButtons.qml
  - surfaces/quickshell/deploy.sh
  - /home/casey/Projects/Pixel3Arch/rootfs-overlay/etc/pulse/default.pa.d/90-blueline-alsa.pa
  - /home/casey/Projects/Pixel3Arch/pkgs/pipewire-abi-provider/PKGBUILD
  - docs/tasks/souveraine-settings-ipc-profiles.md
  - docs/tasks/souveraine-lock-primitives.md
---

# Souveraine native audio and privacy surfaces

## End goal

Souveraine owns the complete audio control and privacy presentation for phone
and laptop. Native PulseAudio is the only audio server. PipeWire exists only as
the video transport required by screen-cast/camera portals; `pipewire-pulse` is
never started and never owns an audio device.

The user and agents see one truthful audio model:

- the real default output and input;
- output/input volume and mute;
- which applications hold playback or recording streams;
- a visible, persistent microphone/camera/screen-capture privacy indicator;
- device switching where more than one real endpoint exists;
- structured IPC methods guarded by the settings/capability authority;
- no ii or PipeWire audio assumptions in the active dependency graph.

This is an original AGPL Souveraine implementation. The current files under
`modules/ii/` are functional scaffolding and behavioral evidence, not the
destination and not files to relabel.

## Ground truth as of 2026-07-18

The important PulseAudio foundation is already present:

- `services/Audio.qml` is PulseAudio-backed and mirrors both the default sink
  and the default source through `pactl`.
- It has real source volume/mute setters, `toggleMicMute()`, `sourceReady`,
  `inputDevices`, and a first `micActive` signal based on Pulse source outputs.
- `VolumeDialogContent.qml` presents a real input page and binds the slider to
  `Audio.source` rather than a fake microphone object.
- `UtilButtons.qml` now calls `Audio.toggleMicMute()` and reads the real source
  mute state.
- Pixel3Arch autoloads the handset capture PCM as the named Pulse source
  `blueline_mic`, makes it the default source, and uses UCM to establish the
  handset route before PulseAudio starts.

Do not reopen those solved questions or restore PipeWire audio. The remaining
work is ownership, completeness, event accuracy, privacy presentation, IPC,
and portal coexistence.

## The architectural boundary

| Function | Authority |
|---|---|
| Speaker/headphone playback | native PulseAudio |
| Handset/external microphone capture | native PulseAudio |
| Per-app audio streams | native PulseAudio sink-input/source-output model |
| Audio device selection | native PulseAudio defaults and profiles |
| Screen-cast frames | PipeWire portal transport |
| Camera frames | PipeWire portal transport where the portal requires it |
| Audio for screen recording | explicit PulseAudio capture chosen by the recorder; never `pipewire-pulse` |

PipeWire's presence must not imply it is an audio server. Packaging and user
units enforce the boundary: no Pulse compatibility daemon, no ALSA device
acquisition by PipeWire, and no competing session policy for sound.

## Owned service model

Replace the scaffolding with an original `SouveraineAudio.qml` service (final
name may remain `Audio` for call-site stability). It projects:

```text
serverReady
defaultSink / defaultSource
sinks[] / sources[]
playbackStreams[] / recordingStreams[]
outputActive / microphoneActive
lastError / generation
```

Each device and stream has a stable, typed shape: Pulse name/index,
description, availability, volume, mute, monitor/physical classification,
application identity, media role, corked/running state, and owning PID when
Pulse exposes it. Do not infer a microphone from `hw:0,1` naming alone;
`blueline_mic` is the board's canonical source name, while laptops may publish
different source names.

### Event model

The current 1.5-second `pactl` snapshot is acceptable diagnostic scaffolding
but not the final privacy mechanism. The owned service keeps one long-lived
`pactl subscribe` observer, debounces server events, then refreshes a complete
machine-readable snapshot. Prefer `pactl --format=json` when supported and a
small owned parser/fallback when it is not; do not parse localized display
labels.

Loss/restart of PulseAudio increments `generation`, clears stale objects, and
repopulates defaults. Sink readiness and source readiness remain independent:
a missing microphone must never disable speaker controls.

Every setter returns a structured result or emits an error with the affected
object. The UI does not optimistically lie when `pactl` fails.

## Privacy truth

Build one owned `PrivacyService` that consumes actual subsystem activity, not
UI toggles:

- `microphoneActive`: at least one Pulse source-output exists; expose the
  application identities and whether streams are corked.
- `cameraActive`: portal/camera session ownership, not merely device presence.
- `screenCaptureActive`: active portal screencast or Souveraine recorder
  session.

The prior borrowed Privacy model must not be reused: converting an array with
`.map(...)` to a boolean is always truthy and is not evidence of access. The
owned implementation uses explicit collection predicates and retains the
participants needed for disclosure.

Privacy indication is not configurable away. Appearance may vary, but active
capture always has a persistent visible indicator on the unlocked shell and a
safe ambient indication on the lock surface. Opening it shows which
application is accessing what and offers a stop/revoke action where the
underlying subsystem supports one. Unknown clients are displayed as unknown,
not hidden.

Muting the microphone and stopping access are distinct operations:

- mute changes the default source's audio value;
- stop/revoke terminates or denies a recording stream/session;
- the indicator remains visible while a client still holds the stream, even if
  the source is muted.

This distinction prevents a muted-but-still-recording process from becoming
invisible.

## Owned surfaces

Create original Souveraine AGPL components under Souveraine paths:

- `services/SouveraineAudio.qml` — authoritative Pulse model and methods.
- `services/PrivacyService.qml` — cross-subsystem access truth.
- `modules/souveraine/audio/VolumeSurface.qml` — output/input tabs, real
  endpoint state, and per-app streams.
- `modules/souveraine/audio/DeviceRow.qml` and `StreamRow.qml` — reusable owned
  presentation primitives.
- `modules/souveraine/privacy/PrivacyIndicator.qml` — persistent compact state.
- `modules/souveraine/privacy/PrivacyDetails.qml` — identities and stop/revoke
  actions.
- phone and laptop hosts that place the same semantic components according to
  their own form-factor contract.

Rewire active bars/sidebars to these components, then remove the corresponding
`modules/ii` overrides and borrowed imports from the active graph. Do not copy
or vendor ii widgets while doing so; use the Souveraine-owned primitive layer.

## IPC and agent reach

Expose a JSON-over-string `audio` IPC surface because Quickshell drops `var`
returns:

```text
audio.state()
audio.devices(direction)
audio.streams(direction)
audio.setVolume(direction, value, reason)
audio.setMuted(direction, muted, reason)
audio.setDefault(direction, pulseName, reason)
audio.stopStream(streamId, reason)
```

Read-only state is ambient. Ordinary volume/mute changes are direct low-risk
settings/actions and audited. Changing a default endpoint is direct while
unlocked unless the settings schema says otherwise. Stopping another
application's recording stream is a physical/privacy action: an agent may
propose it, and human surfaces may act according to the settings authority's
policy. IPC never invents a second authorization model.

All methods validate direction, ranges, endpoint existence, Pulse generation,
and current lock/security state. They return `{ok:false, reason}` rather than
silently no-oping.

## Portal-only PipeWire

The current Pixel3Arch `pipewire-abi-provider` conflicts with the PipeWire
daemon and WirePlumber. That satisfies ABI consumers but makes real portal
screen sharing impossible. Split the packaging policy:

1. retain native PulseAudio and its ALSA ownership;
2. install a real PipeWire daemon plus the minimal session/portal policy needed
   for video nodes;
3. explicitly mask/omit `pipewire-pulse`;
4. prevent WirePlumber/PipeWire from claiming ALSA audio devices, or use a
   portal-video-only configuration if the selected session manager supports
   it;
5. verify `xdg-desktop-portal` and the compositor portal can establish a
   screencast without changing Pulse sinks/sources.

This packaging repair belongs in Pixel3Arch and must be tested as a system
contract. Souveraine surfaces consume its portal state; they do not launch an
ad-hoc competing PipeWire audio stack.

## Verification matrix

Verify on both phone and laptop:

1. Pulse starts with the intended default sink/source and no `pipewire-pulse`
   socket or daemon.
2. Source/sink volume and mute changes round-trip through Pulse and survive a
   service refresh.
3. Starting and stopping recording changes the privacy indicator promptly and
   names the client.
4. Muting during recording leaves the access indicator visible.
5. A Pulse restart clears stale devices/streams and recovers without restarting
   Quickshell.
6. A missing source leaves output controls fully functional.
7. Screen sharing works through the portal-only PipeWire path while Pulse
   remains the sole audio server.
8. Lock/unlock transitions never expose private application metadata on the
   lock surface, but active capture remains visibly indicated.
9. IPC calls return structured success/refusal and cannot bypass the settings
   or step-up authority.

## Done when

- [ ] Active audio/privacy services and surfaces are original Souveraine AGPL
      files with no ii widget dependency in their graph.
- [ ] Native PulseAudio is the sole audio authority on phone and laptop.
- [ ] Real sinks, sources, playback streams, and recording streams are modeled
      eventfully and recover across daemon restart.
- [ ] A persistent truthful privacy indicator identifies active capture clients
      and distinguishes mute from revocation.
- [ ] Owned phone and laptop presentations share semantics without sharing
      inappropriate layout.
- [ ] Agent IPC covers discovery and guarded actions through the common
      settings/capability policy.
- [ ] Portal video works with a real PipeWire transport while `pipewire-pulse`
      remains absent.
- [ ] Pixel 3 microphone capture is verified non-zero through the named
      `blueline_mic` Pulse source; no raw-ALSA-only success is accepted as the
      product result.
