diff --git a/internal/pipewire/core.go b/internal/pipewire/core.go index 9e3613b..7c2b4a9 100644 --- a/internal/pipewire/core.go +++ b/internal/pipewire/core.go @@ -265,3 +265,33 @@ func (c *RegistryGlobal) MarshalBinary() ([]byte, error) { return Marshal(c) } // UnmarshalBinary satisfies [encoding.BinaryUnmarshaler] via [Unmarshal]. func (c *RegistryGlobal) UnmarshalBinary(data []byte) error { return Unmarshal(data, c) } + +// RegistryBind is sent when the client requests to bind to the +// global object with id and use the client proxy with new_id as +// the proxy. After this call, methods can be sent to the remote +// global object and events can be received. +type RegistryBind struct { + // The [RegistryGlobal.ID] to bind to. + ID Int `json:"id"` + // the [RegistryGlobal.Type] of the global id. + Type String `json:"type"` + // The client version of the interface for type. + Version Int `json:"version"` + // The client proxy id for the global object. + NewID Int `json:"new_id"` +} + +// Size satisfies [KnownSize] with a value computed at runtime. +func (c *RegistryBind) Size() Word { + return SizePrefix + + Size(SizeInt) + + SizeString[Word](c.Type) + + Size(SizeInt) + + Size(SizeInt) +} + +// MarshalBinary satisfies [encoding.BinaryMarshaler] via [Marshal]. +func (c *RegistryBind) MarshalBinary() ([]byte, error) { return Marshal(c) } + +// UnmarshalBinary satisfies [encoding.BinaryUnmarshaler] via [Unmarshal]. +func (c *RegistryBind) UnmarshalBinary(data []byte) error { return Unmarshal(data, c) } diff --git a/internal/pipewire/core_test.go b/internal/pipewire/core_test.go index c6d1157..ffea96e 100644 --- a/internal/pipewire/core_test.go +++ b/internal/pipewire/core_test.go @@ -91,10 +91,16 @@ func TestCoreDone(t *testing.T) { t.Parallel() encodingTestCases[pipewire.CoreDone, *pipewire.CoreDone]{ - {"sample", samplePWContainer[1][5][1], pipewire.CoreDone{ + {"sample0", samplePWContainer[1][5][1], pipewire.CoreDone{ ID: -1, Sequence: 0, }, nil}, + + // matches the Core::Sync sample + {"sample1", samplePWContainer[1][41][1], pipewire.CoreDone{ + ID: 0, + Sequence: pipewire.CoreSyncSequenceOffset + 3, + }, nil}, }.run(t) } @@ -102,7 +108,10 @@ func TestCoreBoundProps(t *testing.T) { t.Parallel() encodingTestCases[pipewire.CoreBoundProps, *pipewire.CoreBoundProps]{ - {"sample", samplePWContainer[1][1][1], pipewire.CoreBoundProps{ + + /* recvmsg 0 */ + + {"sample0", samplePWContainer[1][1][1], pipewire.CoreBoundProps{ ID: pipewire.PW_ID_CLIENT, GlobalID: 34, Properties: &pipewire.SPADict{ @@ -114,6 +123,16 @@ func TestCoreBoundProps(t *testing.T) { {Key: "pipewire.sec.gid", Value: "100"}, {Key: "pipewire.sec.socket", Value: "pipewire-0-manager"}}, }, nil}, + + /* recvmsg 1 */ + + {"sample1", samplePWContainer[4][0][1], pipewire.CoreBoundProps{ + ID: 3, + GlobalID: 3, + Properties: &pipewire.SPADict{ + {Key: "object.serial", Value: "3"}, + }, + }, nil}, }.run(t) } @@ -132,7 +151,7 @@ func TestCoreSync(t *testing.T) { encodingTestCases[pipewire.CoreSync, *pipewire.CoreSync]{ {"sample", samplePWContainer[0][3][1], pipewire.CoreSync{ - ID: pipewire.PW_ID_CORE, + ID: 0, Sequence: pipewire.CoreSyncSequenceOffset + 3, }, nil}, }.run(t) @@ -177,7 +196,7 @@ func TestRegistryGlobal(t *testing.T) { }, nil}, {"sample2", samplePWContainer[1][8][1], pipewire.RegistryGlobal{ - ID: 3, // registry takes up 2 + ID: 3, Permissions: pipewire.PW_SECURITY_CONTEXT_PERM_MASK, Type: pipewire.PW_TYPE_INTERFACE_SecurityContext, Version: pipewire.PW_VERSION_SECURITY_CONTEXT, @@ -586,5 +605,36 @@ func TestRegistryGlobal(t *testing.T) { {Key: "application.name", Value: "pw-container"}, }, }, nil}, + + {"sample35", samplePWContainer[1][42][1], pipewire.RegistryGlobal{ + ID: 35, + Permissions: pipewire.PW_CLIENT_PERM_MASK, + Type: pipewire.PW_TYPE_INTERFACE_Client, + Version: pipewire.PW_VERSION_CLIENT, + Properties: &pipewire.SPADict{ + {Key: "object.serial", Value: "35"}, + {Key: "module.id", Value: "2"}, + {Key: "pipewire.protocol", Value: "protocol-native"}, + {Key: "pipewire.sec.pid", Value: "1447"}, + {Key: "pipewire.sec.uid", Value: "1000"}, + {Key: "pipewire.sec.gid", Value: "100"}, + {Key: "pipewire.sec.socket", Value: "pipewire-0-manager"}, + {Key: "pipewire.access", Value: "unrestricted"}, + {Key: "application.name", Value: "WirePlumber"}, + }, + }, nil}, + }.run(t) +} + +func TestRegistryBind(t *testing.T) { + t.Parallel() + + encodingTestCases[pipewire.RegistryBind, *pipewire.RegistryBind]{ + {"sample", samplePWContainer[3][0][1], pipewire.RegistryBind{ + ID: 3, + Type: pipewire.PW_TYPE_INTERFACE_SecurityContext, + Version: pipewire.PW_VERSION_SECURITY_CONTEXT, + NewID: 3, // registry takes up 2 + }, nil}, }.run(t) } diff --git a/internal/pipewire/header_test.go b/internal/pipewire/header_test.go index a35ed98..6369276 100644 --- a/internal/pipewire/header_test.go +++ b/internal/pipewire/header_test.go @@ -11,6 +11,9 @@ func TestHeader(t *testing.T) { t.Parallel() encodingTestCases[pipewire.Header, *pipewire.Header]{ + + /* sendmsg 0 */ + {"PW_CORE_METHOD_HELLO", samplePWContainer[0][0][0], pipewire.Header{ ID: pipewire.PW_ID_CORE, Opcode: pipewire.PW_CORE_METHOD_HELLO, @@ -35,37 +38,39 @@ func TestHeader(t *testing.T) { Size: 0x28, Sequence: 3, FileCount: 0, }, nil}, + /* recvmsg 0 */ + {"PW_CORE_EVENT_INFO", samplePWContainer[1][0][0], pipewire.Header{ ID: pipewire.PW_ID_CORE, Opcode: pipewire.PW_CORE_EVENT_INFO, Size: 0x6b8, Sequence: 0, FileCount: 0, }, nil}, - {"PW_CORE_EVENT_BOUND_PROPS", samplePWContainer[1][1][0], pipewire.Header{ + {"PW_CORE_EVENT_BOUND_PROPS 0", samplePWContainer[1][1][0], pipewire.Header{ ID: pipewire.PW_ID_CORE, Opcode: pipewire.PW_CORE_EVENT_BOUND_PROPS, Size: 0x198, Sequence: 1, FileCount: 0, }, nil}, - {"PW_CLIENT_EVENT_INFO", samplePWContainer[1][2][0], pipewire.Header{ + {"PW_CLIENT_EVENT_INFO 0", samplePWContainer[1][2][0], pipewire.Header{ ID: pipewire.PW_ID_CLIENT, Opcode: pipewire.PW_CLIENT_EVENT_INFO, Size: 0x1f0, Sequence: 2, FileCount: 0, }, nil}, - {"PW_CLIENT_EVENT_INFO*", samplePWContainer[1][3][0], pipewire.Header{ + {"PW_CLIENT_EVENT_INFO 1", samplePWContainer[1][3][0], pipewire.Header{ ID: pipewire.PW_ID_CLIENT, Opcode: pipewire.PW_CLIENT_EVENT_INFO, Size: 0x7a0, Sequence: 3, FileCount: 0, }, nil}, - {"PW_CLIENT_EVENT_INFO**", samplePWContainer[1][4][0], pipewire.Header{ + {"PW_CLIENT_EVENT_INFO 2", samplePWContainer[1][4][0], pipewire.Header{ ID: pipewire.PW_ID_CLIENT, Opcode: pipewire.PW_CLIENT_EVENT_INFO, Size: 0x7d0, Sequence: 4, FileCount: 0, }, nil}, - {"PW_CORE_EVENT_DONE", samplePWContainer[1][5][0], pipewire.Header{ + {"PW_CORE_EVENT_DONE 0", samplePWContainer[1][5][0], pipewire.Header{ ID: pipewire.PW_ID_CORE, Opcode: pipewire.PW_CORE_EVENT_DONE, Size: 0x58, Sequence: 5, FileCount: 0, @@ -281,6 +286,34 @@ func TestHeader(t *testing.T) { Size: 0x238, Sequence: 40, FileCount: 0, }, nil}, + {"PW_CORE_EVENT_DONE 1", samplePWContainer[1][41][0], pipewire.Header{ + ID: pipewire.PW_ID_CORE, + Opcode: pipewire.PW_CORE_EVENT_DONE, + Size: 0x28, Sequence: 41, FileCount: 0, + }, nil}, + + {"PW_REGISTRY_EVENT_GLOBAL 35", samplePWContainer[1][42][0], pipewire.Header{ + ID: 2, + Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL, + Size: 0x268, Sequence: 42, FileCount: 0, + }, nil}, + + /* sendmsg 1 */ + + {"PW_REGISTRY_METHOD_BIND", samplePWContainer[3][0][0], pipewire.Header{ + ID: 2, + Opcode: pipewire.PW_REGISTRY_METHOD_BIND, + Size: 0x98, Sequence: 4, FileCount: 0, + }, nil}, + + /* recvmsg 1 */ + + {"PW_CORE_EVENT_BOUND_PROPS 1", samplePWContainer[4][0][0], pipewire.Header{ + ID: pipewire.PW_ID_CORE, + Opcode: pipewire.PW_CORE_EVENT_BOUND_PROPS, + Size: 0x68, Sequence: 43, FileCount: 0, + }, nil}, + {"PW_SECURITY_CONTEXT_METHOD_CREATE", []byte{ // Id 3, 0, 0, 0, diff --git a/internal/pipewire/pod.go b/internal/pipewire/pod.go index 6e13177..1abc821 100644 --- a/internal/pipewire/pod.go +++ b/internal/pipewire/pod.go @@ -457,7 +457,15 @@ func (f *Footer[T]) MarshalBinary() ([]byte, error) { return Marshal(f) } func (f *Footer[T]) UnmarshalBinary(data []byte) error { return Unmarshal(data, f) } // SPADictItem represents spa_dict_item. -type SPADictItem struct{ Key, Value string } +type SPADictItem struct { + // Dot-separated string. + Key string `json:"key"` + // Arbitrary string. + // + // Integer values are represented in base 10, + // boolean values are represented as "true" or "false". + Value string `json:"value"` +} // SPADict represents spa_dict. type SPADict []SPADictItem