docs: fold the stranded device tasks, archive the handoffs, one index per tree
This commit is contained in:
parent
6ae84f4c52
commit
7b769f7c7f
41 changed files with 207 additions and 85 deletions
252
docs/AUDIO-PRIVACY.md
Normal file
252
docs/AUDIO-PRIVACY.md
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
---
|
||||
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.
|
||||
Loading…
Reference in a new issue