From cfeb7818eb3d7f3e83492bab092adce8f90f83c1 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Tue, 25 Nov 2025 15:15:20 +0900 Subject: [PATCH] internal/pipewire: implement Core::Info and generation footer These are not directly related but are first encountered on the same message in the capture. Signed-off-by: Ophestra --- internal/pipewire/core.go | 45 ++++++++++++++ internal/pipewire/core_test.go | 55 ++++++++++++++++++ internal/pipewire/header_test.go | 6 ++ internal/pipewire/pipewire_test.go | 7 +++ .../testdata/08-recvmsg00-message00-header | Bin 0 -> 16 bytes .../testdata/09-recvmsg00-message00-POD | Bin 0 -> 1672 bytes .../testdata/10-recvmsg00-message00-footer | Bin 0 -> 48 bytes 7 files changed, 113 insertions(+) create mode 100644 internal/pipewire/testdata/08-recvmsg00-message00-header create mode 100644 internal/pipewire/testdata/09-recvmsg00-message00-POD create mode 100644 internal/pipewire/testdata/10-recvmsg00-message00-footer diff --git a/internal/pipewire/core.go b/internal/pipewire/core.go index 7a05208..e5b6abb 100644 --- a/internal/pipewire/core.go +++ b/internal/pipewire/core.go @@ -66,6 +66,51 @@ const ( PW_VERSION_REGISTRY_METHODS = 0 ) +const ( + FOOTER_CORE_OPCODE_GENERATION = iota + + FOOTER_CORE_OPCODE_LAST +) + +// The FooterCoreGeneration indicates to the client what is the current +// registry generation number of the Context on the server side. +// +// The server shall include this footer in the next message it sends that +// follows the increment of the registry generation number. +type FooterCoreGeneration struct { + RegistryGeneration Long +} + +// A CoreInfo event is emitted by the server upon connection +// with the more information about the server. +type CoreInfo struct { + // The id of the server (PW_ID_CORE). + ID Int + // A unique cookie for this server. + Cookie Int + // The name of the user running the server. + UserName String + // The name of the host running the server. + HostName String + // A version string of the server. + Version String + // The name of the server. + Name String + // A set of bits with changes to the info. + ChangeMask Long + // Optional key/value properties, valid when change_mask has PW_CORE_CHANGE_MASK_PROPS. + Props *SPADict +} + +// MarshalBinary satisfies [encoding.BinaryMarshaler] via [Marshal]. +func (c *CoreInfo) MarshalBinary() ([]byte, error) { return Marshal(c) } + +// UnmarshalBinary satisfies [encoding.BinaryUnmarshaler] via [Unmarshal]. +func (c *CoreInfo) UnmarshalBinary(data []byte) error { + _, err := Unmarshal(data, c) + return err +} + // CoreHello is the first message sent by a client. type CoreHello struct { // The version number of the client, usually PW_VERSION_CORE. diff --git a/internal/pipewire/core_test.go b/internal/pipewire/core_test.go index a04013c..661dc39 100644 --- a/internal/pipewire/core_test.go +++ b/internal/pipewire/core_test.go @@ -6,6 +6,61 @@ import ( "hakurei.app/internal/pipewire" ) +func TestFooterCoreGeneration(t *testing.T) { + encodingTestCases[pipewire.Footer[pipewire.FooterCoreGeneration], *pipewire.Footer[pipewire.FooterCoreGeneration]]{ + {"sample", []byte(recvmsg00Message00Footer), pipewire.Footer[pipewire.FooterCoreGeneration]{ + Opcode: pipewire.FOOTER_CORE_OPCODE_GENERATION, + Payload: pipewire.FooterCoreGeneration{RegistryGeneration: 34}, + }, nil}, + }.run(t) +} + +func TestCoreInfo(t *testing.T) { + encodingTestCases[pipewire.CoreInfo, *pipewire.CoreInfo]{ + {"sample", []byte(recvmsg00Message00POD), pipewire.CoreInfo{ + ID: 0, + Cookie: -2069267610, + UserName: "alice", + HostName: "nixos", + Version: "1.4.7", + Name: "pipewire-0", + ChangeMask: pipewire.PW_CORE_CHANGE_MASK_PROPS, + Props: &pipewire.SPADict{NItems: 31, Items: []pipewire.SPADictItem{ + {Key: "config.name", Value: "pipewire.conf"}, + {Key: "application.name", Value: "pipewire"}, + {Key: "application.process.binary", Value: "pipewire"}, + {Key: "application.language", Value: "en_US.UTF-8"}, + {Key: "application.process.id", Value: "1446"}, + {Key: "application.process.user", Value: "alice"}, + {Key: "application.process.host", Value: "nixos"}, + {Key: "window.x11.display", Value: ":0"}, + {Key: "cpu.vm.name", Value: "qemu"}, + {Key: "link.max-buffers", Value: "16"}, + {Key: "core.daemon", Value: "true"}, + {Key: "core.name", Value: "pipewire-0"}, + {Key: "default.clock.min-quantum", Value: "1024"}, + {Key: "cpu.max-align", Value: "32"}, + {Key: "default.clock.rate", Value: "48000"}, + {Key: "default.clock.quantum", Value: "1024"}, + {Key: "default.clock.max-quantum", Value: "2048"}, + {Key: "default.clock.quantum-limit", Value: "8192"}, + {Key: "default.clock.quantum-floor", Value: "4"}, + {Key: "default.video.width", Value: "640"}, + {Key: "default.video.height", Value: "480"}, + {Key: "default.video.rate.num", Value: "25"}, + {Key: "default.video.rate.denom", Value: "1"}, + {Key: "log.level", Value: "2"}, + {Key: "clock.power-of-two-quantum", Value: "true"}, + {Key: "mem.warn-mlock", Value: "false"}, + {Key: "mem.allow-mlock", Value: "true"}, + {Key: "settings.check-quantum", Value: "false"}, + {Key: "settings.check-rate", Value: "false"}, + {Key: "object.id", Value: "0"}, + {Key: "object.serial", Value: "0"}}, + }}, nil}, + }.run(t) +} + func TestCoreHello(t *testing.T) { encodingTestCases[pipewire.CoreHello, *pipewire.CoreHello]{ {"sample", []byte(sendmsg00Message00POD), pipewire.CoreHello{ diff --git a/internal/pipewire/header_test.go b/internal/pipewire/header_test.go index d7ca468..fb13be1 100644 --- a/internal/pipewire/header_test.go +++ b/internal/pipewire/header_test.go @@ -35,6 +35,12 @@ func TestHeader(t *testing.T) { Size: 0x28, Sequence: 3, FileCount: 0, }, nil}, + {"PW_CORE_EVENT_INFO", []byte(recvmsg00Message00Header), pipewire.Header{ + ID: pipewire.PW_ID_CORE, + Opcode: pipewire.PW_CORE_EVENT_INFO, + Size: 0x6b8, Sequence: 0, FileCount: 0, + }, nil}, + {"PW_SECURITY_CONTEXT_METHOD_CREATE", []byte{ // Id 3, 0, 0, 0, diff --git a/internal/pipewire/pipewire_test.go b/internal/pipewire/pipewire_test.go index 28ba329..a577017 100644 --- a/internal/pipewire/pipewire_test.go +++ b/internal/pipewire/pipewire_test.go @@ -24,4 +24,11 @@ var ( sendmsg00Message03Header string //go:embed testdata/07-sendmsg00-message03-POD sendmsg00Message03POD string + + //go:embed testdata/08-recvmsg00-message00-header + recvmsg00Message00Header string + //go:embed testdata/09-recvmsg00-message00-POD + recvmsg00Message00POD string + //go:embed testdata/10-recvmsg00-message00-footer + recvmsg00Message00Footer string ) diff --git a/internal/pipewire/testdata/08-recvmsg00-message00-header b/internal/pipewire/testdata/08-recvmsg00-message00-header new file mode 100644 index 0000000000000000000000000000000000000000..60b6f0b8ecabd870c5579927d1a941cf50415dcf GIT binary patch literal 16 QcmZQzU|`t61|lIC00{B`zW@LL literal 0 HcmV?d00001 diff --git a/internal/pipewire/testdata/09-recvmsg00-message00-POD b/internal/pipewire/testdata/09-recvmsg00-message00-POD new file mode 100644 index 0000000000000000000000000000000000000000..b0fa528d2a19efa1118aac388acbc5476fda267e GIT binary patch literal 1672 zcmZo*V_@K8U|?W@Vg@LH@zbhTwt$4#7#J8h7#J85b25`t85kH~{JhMH{9+`&p`MAJ zIRgU&H&lH=WKoH0mu}H8LSKp42%#i*dS#4<)Pv{Q2ojId1;yHdU=VtAX_1B z1-S!et{zAM0|SE~RDEJWK~83JVo7Fx9zqiX0|O^i9;Od!4TCgP99dsMQGRl2aj{-f zW?o`ZB}64I{i0a(=OpH(mnNoz9SrkFYF>P3uwH10o2~`eKyfVkVD@IFFff3^2;@&g z6B9F#21z{XON&#BKx#l37B8@P!ly4IzqkaX2ZUkzVDTvowXZxgFD1WRufot!FD0|M zASV$T9?VdAD`>pJ{Fz)(s#lha2zr=53R81SLH5AnDJL^8TQ4`ULN}>2EiJVOl!QRi z3@~#I!MTG7b8=!bquilfJJ>uYF<7p z+8JQ}FobAhfTgdT{B*sX)UwnZs5nd=IFJ|^;N=Mm_HT zCTAnu0L$+%b7Ap>rXCcKV4tF^hlNLeQdVkmi5{rP0;_|C2PAe7