PipeWire wire-shape and command-syntax cross-check
Evidence class: dev-host PipeWire session — a real, running PipeWire
1.6.8 daemon on this repository’s own development machine (not the K230
board, not QEMU). This class grounds wire-format and command-syntax
questions, because PipeWire is the same software regardless of host, but it
proves nothing about this board’s audio hardware, ALSA card, or any
hardware volume key — those stay open board tasks (tasks.md, group 7).
No real device on the host session used for this check was muted, had its
volume changed, or was otherwise disturbed; every write below targeted a
throwaway virtual sink created and removed for this check alone, and no
hostname, username, or real stream/app name from the host is reproduced
here.
Method
pactl load-module module-null-sink sink_name=k230_volume_probe \
sink_properties=device.description=K230_Volume_Probe
# -> module id, e.g. 536870914
pw-cli ls Node | grep -B6 k230_volume_probe # -> the node's PipeWire id
wpctl set-volume <id> 0.58
pw-dump | <extract Props for <id>>
wpctl set-mute <id> 1
pw-dump | <extract Props for <id>>
pactl unload-module <module id> # cleanup
Findings
application.icon-name(hyphenated) is the real key, matchingPW_KEY_APP_ICON_NAMEin PipeWire’s ownpipewire/keys.h— confirmed directly against realStream/Output/Audionodes on the host session (several real desktop apps’ own icon names were visible with this exact key spelling; not reproduced here). An earlier draft of this shell’s own parser looked for the underscoredapplication.icon_name, which never matches a real dump.- A
Propsparam’s relevant keys arevolume,mute,channelVolumes,channelMap, plus PipeWire’s ownsoftMute/softVolumes/monitorMute/monitorVolumesthis shell does not use. - The perceptual curve is confirmed, measured directly:
wpctl set-volume <id> 0.58against a throwaway node producedchannelVolumes: [0.195112, 0.195112].0.58^3 = 0.19511199999999995, matching to the precisionwpctlitself prints.wpctlalready applies the same cubic taper this change’s ownvolume::percent_to_linearimplements; the two must (and do) agree. - Mute is independent of level, confirmed directly:
wpctl set-mute <id> 1against the same node changed onlymute: true;channelVolumesstayed exactly[0.195112, 0.195112]. default.audio.sinkmetadata’svaluearrives as an already-parsed JSON object ({"name": "..."}), not a JSON-encoded string, in a realpw-dump/pw-metadatacapture on this PipeWire version, even though its owntypefield readsSpa:String:JSON.pipewire_ipc.rs’sdefault_sink_namereads it as an object for this reason.pw-cli’s scripting/stdin mode does read and execute one command per line with no tty (printf 'info 0\n' | pw-cliproduced a normal banner and command response, then continued reading further lines), and itshelpoutput documentsset-param | s Set param of an object <object-id> <param-id> <param-json>— the exact formpipewire_ipc::set_volume_commandemits (s <id> Props '{...}').- A freshly-connected
pw-clidoes not yet know about objects that existed before it connected, until its own registry sync catches up (s <existing-id> ...issued immediately after connecting returnedError: "s: unknown global '<id>'"). This matters once, at this shell’s own startup when its persistentpw-clifirst connects, not per write — the process stays connected and registry-synced for its whole life after that. pw-metadata’s CLI form ispw-metadata [id [key [value [type]]]]; a real invocation with no arguments printed the currentdefaultmetadata set includingdefault.audio.sink/default.audio.source, confirmingdefault.audio.sink’s key name and value shape match point 5.
Limits
Host-PipeWire-grounded only. Proves the wire shape and command syntax this
shell’s parser and writer assume are correct against a real PipeWire
daemon; proves nothing about the K230’s own ALSA card, kernel driver, or
whether any of this is reachable/correct once cross-compiled and run on
that board. Task groups 6 and 7 in tasks.md carry the QEMU and board
proof this file does not.