Compare commits

..

No commits in common. "wip-pipewire-native" and "master" have entirely different histories.

27 changed files with 40 additions and 2718 deletions

View File

@ -2,6 +2,7 @@ name: Test
on:
- push
- pull_request
jobs:
hakurei:

View File

@ -12,6 +12,8 @@ import (
"time"
"hakurei.app/hst"
"hakurei.app/internal/env"
"hakurei.app/internal/info"
"hakurei.app/internal/outcome"
"hakurei.app/internal/store"
"hakurei.app/message"
@ -21,14 +23,16 @@ import (
func printShowSystem(output io.Writer, short, flagJSON bool) {
t := newPrinter(output)
defer t.MustFlush()
hi := outcome.Info()
hi := &hst.Info{Version: info.Version(), User: new(outcome.Hsu).MustID(nil)}
env.CopyPaths().Copy(&hi.Paths, hi.User)
if flagJSON {
encodeJSON(log.Fatal, output, short, hi)
return
}
t.Printf("Version:\t%s (libwayland %s)\n", hi.Version, hi.WaylandVersion)
t.Printf("Version:\t%s\n", hi.Version)
t.Printf("User:\t%d\n", hi.User)
t.Printf("TempDir:\t%s\n", hi.TempDir)
t.Printf("SharePath:\t%s\n", hi.SharePath)

View File

@ -54,9 +54,6 @@ type Paths struct {
// Info holds basic system information collected from the implementation.
type Info struct {
// WaylandVersion is the libwayland value of WAYLAND_VERSION.
WaylandVersion string `json:"WAYLAND_VERSION"`
// Version is a hardcoded version string.
Version string `json:"version"`
// User is the userid according to hsu.

View File

@ -11,22 +11,10 @@ import (
"hakurei.app/hst"
"hakurei.app/internal/acl"
"hakurei.app/internal/env"
"hakurei.app/internal/info"
"hakurei.app/internal/system"
"hakurei.app/internal/wayland"
"hakurei.app/message"
)
// Info returns the address to a populated [hst.Info].
//
// This must not be called from within package outcome.
func Info() *hst.Info {
hi := hst.Info{WaylandVersion: wayland.Version,
Version: info.Version(), User: new(Hsu).MustID(nil)}
env.CopyPaths().Copy(&hi.Paths, hi.User)
return &hi
}
// envAllocSize is the initial size of the env map pre-allocated when the configured env map is nil.
// It should be large enough to fit all insertions by outcomeOp.toContainer.
const envAllocSize = 1 << 6

View File

@ -1,76 +0,0 @@
package pipewire
/* pipewire/client.h */
const (
PW_TYPE_INTERFACE_Client = PW_TYPE_INFO_INTERFACE_BASE + "Client"
PW_CLIENT_PERM_MASK = PW_PERM_RWXM
PW_VERSION_CLIENT = 3
PW_ID_CLIENT = 1
)
const (
PW_CLIENT_CHANGE_MASK_PROPS = 1 << iota
PW_CLIENT_CHANGE_MASK_ALL = 1<<iota - 1
)
const (
PW_CLIENT_EVENT_INFO = iota
PW_CLIENT_EVENT_PERMISSIONS
PW_CLIENT_EVENT_NUM
PW_VERSION_CLIENT_EVENTS = 0
)
const (
PW_CLIENT_METHOD_ADD_LISTENER = iota
PW_CLIENT_METHOD_ERROR
PW_CLIENT_METHOD_UPDATE_PROPERTIES
PW_CLIENT_METHOD_GET_PERMISSIONS
PW_CLIENT_METHOD_UPDATE_PERMISSIONS
PW_CLIENT_METHOD_NUM
PW_VERSION_CLIENT_METHODS = 0
)
// The ClientInfo event provides client information updates.
// This is emitted when binding to a client or when the client info is updated later.
type ClientInfo struct {
// The global id of the client.
ID Int `json:"id"`
// The changes emitted by this event.
ChangeMask Long `json:"change_mask"`
// Properties of this object, valid when change_mask has PW_CLIENT_CHANGE_MASK_PROPS.
Properties *SPADict `json:"props"`
}
// Size satisfies [KnownSize] with a value computed at runtime.
func (c *ClientInfo) Size() Word {
return SizePrefix +
Size(SizeInt) +
Size(SizeLong) +
c.Properties.Size()
}
// MarshalBinary satisfies [encoding.BinaryMarshaler] via [Marshal].
func (c *ClientInfo) MarshalBinary() ([]byte, error) { return Marshal(c) }
// UnmarshalBinary satisfies [encoding.BinaryUnmarshaler] via [Unmarshal].
func (c *ClientInfo) UnmarshalBinary(data []byte) error { return Unmarshal(data, c) }
// ClientUpdateProperties is used to update the properties of a client.
type ClientUpdateProperties struct {
// Properties to update on the client.
Properties *SPADict `json:"props"`
}
// Size satisfies [KnownSize] with a value computed at runtime.
func (c *ClientUpdateProperties) Size() Word { return SizePrefix + c.Properties.Size() }
// MarshalBinary satisfies [encoding.BinaryMarshaler] via [Marshal].
func (c *ClientUpdateProperties) MarshalBinary() ([]byte, error) { return Marshal(c) }
// UnmarshalBinary satisfies [encoding.BinaryUnmarshaler] via [Unmarshal].
func (c *ClientUpdateProperties) UnmarshalBinary(data []byte) error { return Unmarshal(data, c) }

View File

@ -1,156 +0,0 @@
package pipewire_test
import (
"testing"
"hakurei.app/internal/pipewire"
)
func TestClientInfo(t *testing.T) {
t.Parallel()
encodingTestCases[pipewire.ClientInfo, *pipewire.ClientInfo]{
{"sample", samplePWContainer[1][2][1], pipewire.ClientInfo{
ID: 34,
ChangeMask: pipewire.PW_CLIENT_CHANGE_MASK_PROPS,
Properties: &pipewire.SPADict{
{Key: "pipewire.protocol", Value: "protocol-native"},
{Key: "core.name", Value: "pipewire-0"},
{Key: "pipewire.sec.socket", Value: "pipewire-0-manager"},
{Key: "pipewire.sec.pid", Value: "1443"},
{Key: "pipewire.sec.uid", Value: "1000"},
{Key: "pipewire.sec.gid", Value: "100"},
{Key: "module.id", Value: "2"},
{Key: "object.id", Value: "34"},
{Key: "object.serial", Value: "34"},
}}, nil},
{"sample*", samplePWContainer[1][3][1], pipewire.ClientInfo{
ID: 34,
ChangeMask: pipewire.PW_CLIENT_CHANGE_MASK_PROPS,
Properties: &pipewire.SPADict{
{Key: "pipewire.protocol", Value: "protocol-native"},
{Key: "core.name", Value: "pipewire-alice-1443"},
{Key: "pipewire.sec.socket", Value: "pipewire-0-manager"},
{Key: "pipewire.sec.pid", Value: "1443"},
{Key: "pipewire.sec.uid", Value: "1000"},
{Key: "pipewire.sec.gid", Value: "100"},
{Key: "module.id", Value: "2"},
{Key: "object.id", Value: "34"},
{Key: "object.serial", Value: "34"},
{Key: "remote.intention", Value: "manager"},
{Key: "application.name", Value: "pw-container"},
{Key: "application.process.binary", Value: "pw-container"},
{Key: "application.language", Value: "en_US.UTF-8"},
{Key: "application.process.id", Value: "1443"},
{Key: "application.process.user", Value: "alice"},
{Key: "application.process.host", Value: "nixos"},
{Key: "application.process.session-id", Value: "1"},
{Key: "window.x11.display", Value: ":0"},
{Key: "cpu.vm.name", Value: "qemu"},
{Key: "log.level", Value: "0"},
{Key: "cpu.max-align", Value: "32"},
{Key: "default.clock.rate", Value: "48000"},
{Key: "default.clock.quantum", Value: "1024"},
{Key: "default.clock.min-quantum", Value: "32"},
{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: "clock.power-of-two-quantum", Value: "true"},
{Key: "link.max-buffers", Value: "64"},
{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: "core.version", Value: "1.4.7"},
}}, nil},
{"sample**", samplePWContainer[1][4][1], pipewire.ClientInfo{
ID: 34,
ChangeMask: pipewire.PW_CLIENT_CHANGE_MASK_PROPS,
Properties: &pipewire.SPADict{
{Key: "pipewire.protocol", Value: "protocol-native"},
{Key: "core.name", Value: "pipewire-alice-1443"},
{Key: "pipewire.sec.socket", Value: "pipewire-0-manager"},
{Key: "pipewire.sec.pid", Value: "1443"},
{Key: "pipewire.sec.uid", Value: "1000"},
{Key: "pipewire.sec.gid", Value: "100"},
{Key: "module.id", Value: "2"},
{Key: "object.id", Value: "34"},
{Key: "object.serial", Value: "34"},
{Key: "remote.intention", Value: "manager"},
{Key: "application.name", Value: "pw-container"},
{Key: "application.process.binary", Value: "pw-container"},
{Key: "application.language", Value: "en_US.UTF-8"},
{Key: "application.process.id", Value: "1443"},
{Key: "application.process.user", Value: "alice"},
{Key: "application.process.host", Value: "nixos"},
{Key: "application.process.session-id", Value: "1"},
{Key: "window.x11.display", Value: ":0"},
{Key: "cpu.vm.name", Value: "qemu"},
{Key: "log.level", Value: "0"},
{Key: "cpu.max-align", Value: "32"},
{Key: "default.clock.rate", Value: "48000"},
{Key: "default.clock.quantum", Value: "1024"},
{Key: "default.clock.min-quantum", Value: "32"},
{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: "clock.power-of-two-quantum", Value: "true"},
{Key: "link.max-buffers", Value: "64"},
{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: "core.version", Value: "1.4.7"},
{Key: "pipewire.access", Value: "unrestricted"},
}}, nil},
}.run(t)
}
func TestClientUpdateProperties(t *testing.T) {
t.Parallel()
encodingTestCases[pipewire.ClientUpdateProperties, *pipewire.ClientUpdateProperties]{
{"sample", samplePWContainer[0][1][1], pipewire.ClientUpdateProperties{Properties: &pipewire.SPADict{
{Key: "remote.intention", Value: "manager"},
{Key: "application.name", Value: "pw-container"},
{Key: "application.process.binary", Value: "pw-container"},
{Key: "application.language", Value: "en_US.UTF-8"},
{Key: "application.process.id", Value: "1443"},
{Key: "application.process.user", Value: "alice"},
{Key: "application.process.host", Value: "nixos"},
{Key: "application.process.session-id", Value: "1"},
{Key: "window.x11.display", Value: ":0"},
{Key: "cpu.vm.name", Value: "qemu"},
{Key: "log.level", Value: "0"},
{Key: "cpu.max-align", Value: "32"},
{Key: "default.clock.rate", Value: "48000"},
{Key: "default.clock.quantum", Value: "1024"},
{Key: "default.clock.min-quantum", Value: "32"},
{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: "clock.power-of-two-quantum", Value: "true"},
{Key: "link.max-buffers", Value: "64"},
{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: "core.version", Value: "1.4.7"},
{Key: "core.name", Value: "pipewire-alice-1443"},
}}, nil},
}.run(t)
}

View File

@ -1,257 +0,0 @@
package pipewire
/* pipewire/core.h */
const (
PW_TYPE_INTERFACE_Core = PW_TYPE_INFO_INTERFACE_BASE + "Core"
PW_TYPE_INTERFACE_Registry = PW_TYPE_INFO_INTERFACE_BASE + "Registry"
PW_CORE_PERM_MASK = PW_PERM_R | PW_PERM_X | PW_PERM_M
PW_VERSION_CORE = 4
PW_VERSION_REGISTRY = 3
PW_DEFAULT_REMOTE = "pipewire-0"
PW_ID_CORE = 0
PW_ID_ANY = Word(0xffffffff)
)
const (
PW_CORE_CHANGE_MASK_PROPS = 1 << iota
PW_CORE_CHANGE_MASK_ALL = 1<<iota - 1
)
const (
PW_CORE_EVENT_INFO = iota
PW_CORE_EVENT_DONE
PW_CORE_EVENT_PING
PW_CORE_EVENT_ERROR
PW_CORE_EVENT_REMOVE_ID
PW_CORE_EVENT_BOUND_ID
PW_CORE_EVENT_ADD_MEM
PW_CORE_EVENT_REMOVE_MEM
PW_CORE_EVENT_BOUND_PROPS
PW_CORE_EVENT_NUM
PW_VERSION_CORE_EVENTS = 1
)
const (
PW_CORE_METHOD_ADD_LISTENER = iota
PW_CORE_METHOD_HELLO
PW_CORE_METHOD_SYNC
PW_CORE_METHOD_PONG
PW_CORE_METHOD_ERROR
PW_CORE_METHOD_GET_REGISTRY
PW_CORE_METHOD_CREATE_OBJECT
PW_CORE_METHOD_DESTROY
PW_CORE_METHOD_NUM
PW_VERSION_CORE_METHODS = 0
)
const (
PW_REGISTRY_EVENT_GLOBAL = iota
PW_REGISTRY_EVENT_GLOBAL_REMOVE
PW_REGISTRY_EVENT_NUM
PW_VERSION_REGISTRY_EVENTS = 0
)
const (
PW_REGISTRY_METHOD_ADD_LISTENER = iota
PW_REGISTRY_METHOD_BIND
PW_REGISTRY_METHOD_DESTROY
PW_REGISTRY_METHOD_NUM
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 `json:"registry_generation"`
}
// 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 `json:"id"`
// A unique cookie for this server.
Cookie Int `json:"cookie"`
// The name of the user running the server.
UserName String `json:"user_name"`
// The name of the host running the server.
HostName String `json:"host_name"`
// A version string of the server.
Version String `json:"version"`
// The name of the server.
Name String `json:"name"`
// A set of bits with changes to the info.
ChangeMask Long `json:"change_mask"`
// Optional key/value properties, valid when change_mask has PW_CORE_CHANGE_MASK_PROPS.
Properties *SPADict `json:"props"`
}
// Size satisfies [KnownSize] with a value computed at runtime.
func (c *CoreInfo) Size() Word {
return SizePrefix +
Size(SizeInt) +
Size(SizeInt) +
SizeString[Word](c.UserName) +
SizeString[Word](c.HostName) +
SizeString[Word](c.Version) +
SizeString[Word](c.Name) +
Size(SizeLong) +
c.Properties.Size()
}
// 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 { return Unmarshal(data, c) }
// The CoreDone event is emitted as a result of a client Sync method.
type CoreDone struct {
// Passed from [CoreSync.ID].
ID Int `json:"id"`
// Passed from [CoreSync.Sequence].
Sequence Int `json:"sequence"`
}
// Size satisfies [KnownSize] with a constant value.
func (c *CoreDone) Size() Word { return SizePrefix + Size(SizeInt) + Size(SizeInt) }
// MarshalBinary satisfies [encoding.BinaryMarshaler] via [Marshal].
func (c *CoreDone) MarshalBinary() ([]byte, error) { return Marshal(c) }
// UnmarshalBinary satisfies [encoding.BinaryUnmarshaler] via [Unmarshal].
func (c *CoreDone) UnmarshalBinary(data []byte) error { return Unmarshal(data, c) }
// The CoreBoundProps event is emitted when a local object ID is bound to a global ID.
// It is emitted before the global becomes visible in the registry.
type CoreBoundProps struct {
// A proxy id.
ID Int `json:"id"`
// The global_id as it will appear in the registry.
GlobalID Int `json:"global_id"`
// The properties of the global.
Properties *SPADict `json:"props"`
}
// Size satisfies [KnownSize] with a value computed at runtime.
func (c *CoreBoundProps) Size() Word {
return SizePrefix +
Size(SizeInt) +
Size(SizeInt) +
c.Properties.Size()
}
// MarshalBinary satisfies [encoding.BinaryMarshaler] via [Marshal].
func (c *CoreBoundProps) MarshalBinary() ([]byte, error) { return Marshal(c) }
// UnmarshalBinary satisfies [encoding.BinaryUnmarshaler] via [Unmarshal].
func (c *CoreBoundProps) UnmarshalBinary(data []byte) error { return Unmarshal(data, c) }
// CoreHello is the first message sent by a client.
type CoreHello struct {
// The version number of the client, usually PW_VERSION_CORE.
Version Int `json:"version"`
}
// Size satisfies [KnownSize] with a constant value.
func (c *CoreHello) Size() Word { return SizePrefix + Size(SizeInt) }
// MarshalBinary satisfies [encoding.BinaryMarshaler] via [Marshal].
func (c *CoreHello) MarshalBinary() ([]byte, error) { return Marshal(c) }
// UnmarshalBinary satisfies [encoding.BinaryUnmarshaler] via [Unmarshal].
func (c *CoreHello) UnmarshalBinary(data []byte) error { return Unmarshal(data, c) }
const (
// CoreSyncSequenceOffset is the offset to [Header.Sequence] to produce [CoreSync.Sequence].
CoreSyncSequenceOffset = 0x40000000
)
// The CoreSync message will result in a Done event from the server.
// When the Done event is received, the client can be sure that all
// operations before the Sync method have been completed.
type CoreSync struct {
// The id will be returned in the Done event.
ID Int `json:"id"`
// Usually generated automatically and will be returned in the Done event.
Sequence Int `json:"sequence"`
}
// Size satisfies [KnownSize] with a constant value.
func (c *CoreSync) Size() Word { return SizePrefix + Size(SizeInt) + Size(SizeInt) }
// MarshalBinary satisfies [encoding.BinaryMarshaler] via [Marshal].
func (c *CoreSync) MarshalBinary() ([]byte, error) { return Marshal(c) }
// UnmarshalBinary satisfies [encoding.BinaryUnmarshaler] via [Unmarshal].
func (c *CoreSync) UnmarshalBinary(data []byte) error { return Unmarshal(data, c) }
// CoreGetRegistry is sent when a client requests to bind to the
// registry object and list the available objects on the server.
//
// Like with all bindings, first the client allocates a new proxy
// id and puts this as the new_id field. Methods and Events can
// then be sent and received on the new_id (in the message Id field).
type CoreGetRegistry struct {
// The version of the registry interface used on the client,
// usually PW_VERSION_REGISTRY.
Version Int `json:"version"`
// The id of the new proxy with the registry interface,
// ends up as [Header.ID] in future messages.
NewID Int `json:"new_id"`
}
// Size satisfies [KnownSize] with a constant value.
func (c *CoreGetRegistry) Size() Word { return SizePrefix + Size(SizeInt) + Size(SizeInt) }
// MarshalBinary satisfies [encoding.BinaryMarshaler] via [Marshal].
func (c *CoreGetRegistry) MarshalBinary() ([]byte, error) { return Marshal(c) }
// UnmarshalBinary satisfies [encoding.BinaryUnmarshaler] via [Unmarshal].
func (c *CoreGetRegistry) UnmarshalBinary(data []byte) error { return Unmarshal(data, c) }
// A RegistryGlobal event is emitted to notify a client about a new global object.
type RegistryGlobal struct {
// The global id.
ID Int `json:"id"`
// Permission bits.
Permissions Int `json:"permissions"`
// The type of object.
Type String `json:"type"`
// The server version of the object.
Version Int `json:"version"`
// Extra global properties.
Properties *SPADict `json:"props"`
}
// Size satisfies [KnownSize] with a value computed at runtime.
func (c *RegistryGlobal) Size() Word {
return SizePrefix +
Size(SizeInt) +
Size(SizeInt) +
SizeString[Word](c.Type) +
Size(SizeInt) +
c.Properties.Size()
}
// MarshalBinary satisfies [encoding.BinaryMarshaler] via [Marshal].
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) }

View File

@ -1,573 +0,0 @@
package pipewire_test
import (
"testing"
"hakurei.app/internal/pipewire"
)
func TestFooterCoreGeneration(t *testing.T) {
t.Parallel()
encodingTestCases[pipewire.Footer[pipewire.FooterCoreGeneration], *pipewire.Footer[pipewire.FooterCoreGeneration]]{
{"sample", samplePWContainer[1][0][2], pipewire.Footer[pipewire.FooterCoreGeneration]{
Opcode: pipewire.FOOTER_CORE_OPCODE_GENERATION,
Payload: pipewire.FooterCoreGeneration{RegistryGeneration: 0x22},
}, nil},
{"sample*", samplePWContainer[1][5][2], pipewire.Footer[pipewire.FooterCoreGeneration]{
Opcode: pipewire.FOOTER_CORE_OPCODE_GENERATION,
Payload: pipewire.FooterCoreGeneration{RegistryGeneration: 0x23},
}, nil},
}.run(t)
}
func TestCoreInfo(t *testing.T) {
t.Parallel()
encodingTestCases[pipewire.CoreInfo, *pipewire.CoreInfo]{
{"sample", samplePWContainer[1][0][1], pipewire.CoreInfo{
ID: 0,
Cookie: -2069267610,
UserName: "alice",
HostName: "nixos",
Version: "1.4.7",
Name: "pipewire-0",
ChangeMask: pipewire.PW_CORE_CHANGE_MASK_PROPS,
Properties: &pipewire.SPADict{
{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 TestCoreDone(t *testing.T) {
t.Parallel()
encodingTestCases[pipewire.CoreDone, *pipewire.CoreDone]{
{"sample", samplePWContainer[1][5][1], pipewire.CoreDone{
ID: -1,
Sequence: 0,
}, nil},
}.run(t)
}
func TestCoreBoundProps(t *testing.T) {
t.Parallel()
encodingTestCases[pipewire.CoreBoundProps, *pipewire.CoreBoundProps]{
{"sample", samplePWContainer[1][1][1], pipewire.CoreBoundProps{
ID: pipewire.PW_ID_CLIENT,
GlobalID: 34,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "34"},
{Key: "module.id", Value: "2"},
{Key: "pipewire.protocol", Value: "protocol-native"},
{Key: "pipewire.sec.pid", Value: "1443"},
{Key: "pipewire.sec.uid", Value: "1000"},
{Key: "pipewire.sec.gid", Value: "100"},
{Key: "pipewire.sec.socket", Value: "pipewire-0-manager"}},
}, nil},
}.run(t)
}
func TestCoreHello(t *testing.T) {
t.Parallel()
encodingTestCases[pipewire.CoreHello, *pipewire.CoreHello]{
{"sample", samplePWContainer[0][0][1], pipewire.CoreHello{
Version: pipewire.PW_VERSION_CORE,
}, nil},
}.run(t)
}
func TestCoreSync(t *testing.T) {
t.Parallel()
encodingTestCases[pipewire.CoreSync, *pipewire.CoreSync]{
{"sample", samplePWContainer[0][3][1], pipewire.CoreSync{
ID: pipewire.PW_ID_CORE,
Sequence: pipewire.CoreSyncSequenceOffset + 3,
}, nil},
}.run(t)
}
func TestCoreGetRegistry(t *testing.T) {
t.Parallel()
encodingTestCases[pipewire.CoreGetRegistry, *pipewire.CoreGetRegistry]{
{"sample", samplePWContainer[0][2][1], pipewire.CoreGetRegistry{
Version: pipewire.PW_VERSION_REGISTRY,
// this ends up as the Id of PW_TYPE_INTERFACE_Registry
NewID: 2,
}, nil},
}.run(t)
}
func TestRegistryGlobal(t *testing.T) {
t.Parallel()
encodingTestCases[pipewire.RegistryGlobal, *pipewire.RegistryGlobal]{
{"sample0", samplePWContainer[1][6][1], pipewire.RegistryGlobal{
ID: pipewire.PW_ID_CORE,
Permissions: pipewire.PW_CORE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Core,
Version: pipewire.PW_VERSION_CORE,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "0"},
{Key: "core.name", Value: "pipewire-0"},
},
}, nil},
{"sample1", samplePWContainer[1][7][1], pipewire.RegistryGlobal{
ID: 1,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "1"},
{Key: "module.name", Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-rt"},
},
}, nil},
{"sample2", samplePWContainer[1][8][1], pipewire.RegistryGlobal{
ID: 3, // registry takes up 2
Permissions: pipewire.PW_SECURITY_CONTEXT_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_SecurityContext,
Version: pipewire.PW_VERSION_SECURITY_CONTEXT,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "3"},
},
}, nil},
{"sample3", samplePWContainer[1][9][1], pipewire.RegistryGlobal{
ID: 2,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "2"},
{Key: "module.name", Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-protocol-native"},
},
}, nil},
{"sample4", samplePWContainer[1][10][1], pipewire.RegistryGlobal{
ID: 5,
Permissions: pipewire.PW_PROFILER_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Profiler,
Version: pipewire.PW_VERSION_PROFILER,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "5"},
},
}, nil},
{"sample5", samplePWContainer[1][11][1], pipewire.RegistryGlobal{
ID: 4,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "4"},
{Key: "module.name", Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-profiler"},
},
}, nil},
{"sample6", samplePWContainer[1][12][1], pipewire.RegistryGlobal{
ID: 6,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "6"},
{Key: "module.name", Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-metadata"},
},
}, nil},
{"sample7", samplePWContainer[1][13][1], pipewire.RegistryGlobal{
ID: 7,
Permissions: pipewire.PW_FACTORY_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Factory,
Version: pipewire.PW_VERSION_FACTORY,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "7"},
{Key: "module.id", Value: "6"},
{Key: "factory.name", Value: "metadata"},
{Key: "factory.type.name", Value: pipewire.PW_TYPE_INTERFACE_Metadata},
{Key: "factory.type.version", Value: "3"},
},
}, nil},
{"sample8", samplePWContainer[1][14][1], pipewire.RegistryGlobal{
ID: 8,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "8"},
{Key: "module.name", Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-spa-device-factory"},
},
}, nil},
{"sample9", samplePWContainer[1][15][1], pipewire.RegistryGlobal{
ID: 9,
Permissions: pipewire.PW_FACTORY_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Factory,
Version: pipewire.PW_VERSION_FACTORY,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "9"},
{Key: "module.id", Value: "8"},
{Key: "factory.name", Value: "spa-device-factory"},
{Key: "factory.type.name", Value: pipewire.PW_TYPE_INTERFACE_Device},
{Key: "factory.type.version", Value: "3"},
},
}, nil},
{"sample10", samplePWContainer[1][16][1], pipewire.RegistryGlobal{
ID: 10,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "10"},
{Key: "module.name", Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-spa-node-factory"},
},
}, nil},
{"sample11", samplePWContainer[1][17][1], pipewire.RegistryGlobal{
ID: 11,
Permissions: pipewire.PW_FACTORY_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Factory,
Version: pipewire.PW_VERSION_FACTORY,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "11"},
{Key: "module.id", Value: "10"},
{Key: "factory.name", Value: "spa-node-factory"},
{Key: "factory.type.name", Value: pipewire.PW_TYPE_INTERFACE_Node},
{Key: "factory.type.version", Value: "3"},
},
}, nil},
{"sample12", samplePWContainer[1][18][1], pipewire.RegistryGlobal{
ID: 12,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "12"},
{Key: "module.name", Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-client-node"},
},
}, nil},
{"sample13", samplePWContainer[1][19][1], pipewire.RegistryGlobal{
ID: 13,
Permissions: pipewire.PW_FACTORY_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Factory,
Version: pipewire.PW_VERSION_FACTORY,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "13"},
{Key: "module.id", Value: "12"},
{Key: "factory.name", Value: "client-node"},
{Key: "factory.type.name", Value: pipewire.PW_TYPE_INTERFACE_ClientNode},
{Key: "factory.type.version", Value: "6"},
},
}, nil},
{"sample14", samplePWContainer[1][20][1], pipewire.RegistryGlobal{
ID: 14,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "14"},
{Key: "module.name", Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-client-device"},
},
}, nil},
{"sample15", samplePWContainer[1][21][1], pipewire.RegistryGlobal{
ID: 15,
Permissions: pipewire.PW_FACTORY_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Factory,
Version: pipewire.PW_VERSION_FACTORY,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "15"},
{Key: "module.id", Value: "14"},
{Key: "factory.name", Value: "client-device"},
{Key: "factory.type.name", Value: "Spa:Pointer:Interface:Device"},
{Key: "factory.type.version", Value: "0"},
},
}, nil},
{"sample16", samplePWContainer[1][22][1], pipewire.RegistryGlobal{
ID: 16,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "16"},
{Key: "module.name", Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-portal"},
},
}, nil},
{"sample17", samplePWContainer[1][23][1], pipewire.RegistryGlobal{
ID: 17,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "17"},
{Key: "module.name", Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-access"},
},
}, nil},
{"sample18", samplePWContainer[1][24][1], pipewire.RegistryGlobal{
ID: 18,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "18"},
{Key: "module.name", Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-adapter"},
},
}, nil},
{"sample19", samplePWContainer[1][25][1], pipewire.RegistryGlobal{
ID: 19,
Permissions: pipewire.PW_FACTORY_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Factory,
Version: pipewire.PW_VERSION_FACTORY,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "19"},
{Key: "module.id", Value: "18"},
{Key: "factory.name", Value: "adapter"},
{Key: "factory.type.name", Value: pipewire.PW_TYPE_INTERFACE_Node},
{Key: "factory.type.version", Value: "3"},
},
}, nil},
{"sample20", samplePWContainer[1][26][1], pipewire.RegistryGlobal{
ID: 20,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "20"},
{Key: "module.name", Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-link-factory"},
},
}, nil},
{"sample21", samplePWContainer[1][27][1], pipewire.RegistryGlobal{
ID: 21,
Permissions: pipewire.PW_FACTORY_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Factory,
Version: pipewire.PW_VERSION_FACTORY,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "21"},
{Key: "module.id", Value: "20"},
{Key: "factory.name", Value: "link-factory"},
{Key: "factory.type.name", Value: pipewire.PW_TYPE_INTERFACE_Link},
{Key: "factory.type.version", Value: "3"},
},
}, nil},
{"sample22", samplePWContainer[1][28][1], pipewire.RegistryGlobal{
ID: 22,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "22"},
{Key: "module.name", Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-session-manager"},
},
}, nil},
{"sample23", samplePWContainer[1][29][1], pipewire.RegistryGlobal{
ID: 23,
Permissions: pipewire.PW_FACTORY_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Factory,
Version: pipewire.PW_VERSION_FACTORY,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "23"},
{Key: "module.id", Value: "22"},
{Key: "factory.name", Value: "client-endpoint"},
{Key: "factory.type.name", Value: "PipeWire:Interface:ClientEndpoint"},
{Key: "factory.type.version", Value: "0"},
},
}, nil},
{"sample24", samplePWContainer[1][30][1], pipewire.RegistryGlobal{
ID: 24,
Permissions: pipewire.PW_FACTORY_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Factory,
Version: pipewire.PW_VERSION_FACTORY,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "24"},
{Key: "module.id", Value: "22"},
{Key: "factory.name", Value: "client-session"},
{Key: "factory.type.name", Value: "PipeWire:Interface:ClientSession"},
{Key: "factory.type.version", Value: "0"},
},
}, nil},
{"sample25", samplePWContainer[1][31][1], pipewire.RegistryGlobal{
ID: 25,
Permissions: pipewire.PW_FACTORY_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Factory,
Version: pipewire.PW_VERSION_FACTORY,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "25"},
{Key: "module.id", Value: "22"},
{Key: "factory.name", Value: "session"},
{Key: "factory.type.name", Value: "PipeWire:Interface:Session"},
{Key: "factory.type.version", Value: "0"},
},
}, nil},
{"sample26", samplePWContainer[1][32][1], pipewire.RegistryGlobal{
ID: 26,
Permissions: pipewire.PW_FACTORY_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Factory,
Version: pipewire.PW_VERSION_FACTORY,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "26"},
{Key: "module.id", Value: "22"},
{Key: "factory.name", Value: "endpoint"},
{Key: "factory.type.name", Value: "PipeWire:Interface:Endpoint"},
{Key: "factory.type.version", Value: "0"},
},
}, nil},
{"sample27", samplePWContainer[1][33][1], pipewire.RegistryGlobal{
ID: 27,
Permissions: pipewire.PW_FACTORY_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Factory,
Version: pipewire.PW_VERSION_FACTORY,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "27"},
{Key: "module.id", Value: "22"},
{Key: "factory.name", Value: "endpoint-stream"},
{Key: "factory.type.name", Value: "PipeWire:Interface:EndpointStream"},
{Key: "factory.type.version", Value: "0"},
},
}, nil},
{"sample28", samplePWContainer[1][34][1], pipewire.RegistryGlobal{
ID: 28,
Permissions: pipewire.PW_FACTORY_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Factory,
Version: pipewire.PW_VERSION_FACTORY,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "28"},
{Key: "module.id", Value: "22"},
{Key: "factory.name", Value: "endpoint-link"},
{Key: "factory.type.name", Value: "PipeWire:Interface:EndpointLink"},
{Key: "factory.type.version", Value: "0"},
},
}, nil},
{"sample29", samplePWContainer[1][35][1], pipewire.RegistryGlobal{
ID: 29,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "29"},
{Key: "module.name", Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-x11-bell"},
},
}, nil},
{"sample30", samplePWContainer[1][36][1], pipewire.RegistryGlobal{
ID: 30,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "30"},
{Key: "module.name", Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-jackdbus-detect"},
},
}, nil},
{"sample31", samplePWContainer[1][37][1], pipewire.RegistryGlobal{
ID: 31,
Permissions: pipewire.PW_PERM_RWXM, // why is this not PW_NODE_PERM_MASK?
Type: pipewire.PW_TYPE_INTERFACE_Node,
Version: pipewire.PW_VERSION_NODE,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "31"},
{Key: "factory.id", Value: "11"},
{Key: "priority.driver", Value: "200000"},
{Key: "node.name", Value: "Dummy-Driver"},
},
}, nil},
{"sample32", samplePWContainer[1][38][1], pipewire.RegistryGlobal{
ID: 32,
Permissions: pipewire.PW_PERM_RWXM, // why is this not PW_NODE_PERM_MASK?
Type: pipewire.PW_TYPE_INTERFACE_Node,
Version: pipewire.PW_VERSION_NODE,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "32"},
{Key: "factory.id", Value: "11"},
{Key: "priority.driver", Value: "190000"},
{Key: "node.name", Value: "Freewheel-Driver"},
},
}, nil},
{"sample33", samplePWContainer[1][39][1], pipewire.RegistryGlobal{
ID: 33,
Permissions: pipewire.PW_METADATA_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Metadata,
Version: pipewire.PW_VERSION_METADATA,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "33"},
{Key: "metadata.name", Value: "settings"},
},
}, nil},
{"sample34", samplePWContainer[1][40][1], pipewire.RegistryGlobal{
ID: 34,
Permissions: pipewire.PW_CLIENT_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Client,
Version: pipewire.PW_VERSION_CLIENT,
Properties: &pipewire.SPADict{
{Key: "object.serial", Value: "34"},
{Key: "module.id", Value: "2"},
{Key: "pipewire.protocol", Value: "protocol-native"},
{Key: "pipewire.sec.pid", Value: "1443"},
{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: "pw-container"},
},
}, nil},
}.run(t)
}

View File

@ -1,73 +0,0 @@
package pipewire
import (
"encoding/binary"
"errors"
)
const (
// SizeHeader is the fixed size of [Header].
SizeHeader = 16
// SizeMax is the largest value of [Header.Size] that can be represented in its 3-byte segment.
SizeMax = 0x00ffffff
)
var (
// ErrSizeRange indicates that the value of [Header.Size] cannot be represented in its 3-byte segment.
ErrSizeRange = errors.New("size out of range")
// ErrBadHeader indicates that the header slice does not have length [HeaderSize].
ErrBadHeader = errors.New("incorrect header size")
)
// A Header is the fixed-size message header described in protocol native.
type Header struct {
// The message id this is the destination resource/proxy id.
ID Word `json:"Id"`
// The opcode on the resource/proxy interface.
Opcode byte `json:"opcode"`
// The size of the payload and optional footer of the message.
// Note: this value is only 24 bits long in the format.
Size uint32 `json:"size"`
// An increasing sequence number for each message.
Sequence Word `json:"seq"`
// Number of file descriptors in this message.
FileCount Word `json:"n_fds"`
}
// append appends the protocol native message header to data.
//
// Callers must perform bounds check on [Header.Size].
func (h *Header) append(data []byte) []byte {
data = binary.NativeEndian.AppendUint32(data, h.ID)
data = binary.NativeEndian.AppendUint32(data, Word(h.Opcode)<<24|h.Size)
data = binary.NativeEndian.AppendUint32(data, h.Sequence)
data = binary.NativeEndian.AppendUint32(data, h.FileCount)
return data
}
// MarshalBinary encodes the protocol native message header.
func (h *Header) MarshalBinary() (data []byte, err error) {
if h.Size&^SizeMax != 0 {
return nil, ErrSizeRange
}
return h.append(make([]byte, 0, SizeHeader)), nil
}
// unmarshalBinary decodes the protocol native message header.
func (h *Header) unmarshalBinary(data [SizeHeader]byte) {
h.ID = binary.NativeEndian.Uint32(data[0:4])
h.Size = binary.NativeEndian.Uint32(data[4:8])
h.Opcode = byte(h.Size >> 24)
h.Size &= SizeMax
h.Sequence = binary.NativeEndian.Uint32(data[8:])
h.FileCount = binary.NativeEndian.Uint32(data[12:])
}
// UnmarshalBinary decodes the protocol native message header.
func (h *Header) UnmarshalBinary(data []byte) error {
if len(data) != SizeHeader {
return ErrBadHeader
}
h.unmarshalBinary(([SizeHeader]byte)(data))
return nil
}

View File

@ -1,328 +0,0 @@
package pipewire_test
import (
"reflect"
"testing"
"hakurei.app/internal/pipewire"
)
func TestHeader(t *testing.T) {
t.Parallel()
encodingTestCases[pipewire.Header, *pipewire.Header]{
{"PW_CORE_METHOD_HELLO", samplePWContainer[0][0][0], pipewire.Header{
ID: pipewire.PW_ID_CORE,
Opcode: pipewire.PW_CORE_METHOD_HELLO,
Size: 0x18, Sequence: 0, FileCount: 0,
}, nil},
{"PW_CLIENT_METHOD_UPDATE_PROPERTIES", samplePWContainer[0][1][0], pipewire.Header{
ID: pipewire.PW_ID_CLIENT,
Opcode: pipewire.PW_CLIENT_METHOD_UPDATE_PROPERTIES,
Size: 0x600, Sequence: 1, FileCount: 0,
}, nil},
{"PW_CORE_METHOD_GET_REGISTRY", samplePWContainer[0][2][0], pipewire.Header{
ID: pipewire.PW_ID_CORE,
Opcode: pipewire.PW_CORE_METHOD_GET_REGISTRY,
Size: 0x28, Sequence: 2, FileCount: 0,
}, nil},
{"PW_CORE_METHOD_SYNC", samplePWContainer[0][3][0], pipewire.Header{
ID: pipewire.PW_ID_CORE,
Opcode: pipewire.PW_CORE_METHOD_SYNC,
Size: 0x28, Sequence: 3, FileCount: 0,
}, nil},
{"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{
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{
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{
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{
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{
ID: pipewire.PW_ID_CORE,
Opcode: pipewire.PW_CORE_EVENT_DONE,
Size: 0x58, Sequence: 5, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 0", samplePWContainer[1][6][0], pipewire.Header{
ID: 2, // this is specified by Core::GetRegistry in samplePWContainer[0][2][1]
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0xc8, Sequence: 6, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 1", samplePWContainer[1][7][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0xd8, Sequence: 7, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 2", samplePWContainer[1][8][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0xa8, Sequence: 8, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 3", samplePWContainer[1][9][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0xe8, Sequence: 9, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 4", samplePWContainer[1][10][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0xa0, Sequence: 10, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 5", samplePWContainer[1][11][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0xe0, Sequence: 11, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 6", samplePWContainer[1][12][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0xe0, Sequence: 12, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 7", samplePWContainer[1][13][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0x170, Sequence: 13, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 8", samplePWContainer[1][14][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0xe8, Sequence: 14, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 9", samplePWContainer[1][15][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0x178, Sequence: 15, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 10", samplePWContainer[1][16][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0xe8, Sequence: 16, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 11", samplePWContainer[1][17][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0x170, Sequence: 17, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 12", samplePWContainer[1][18][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0xe0, Sequence: 18, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 13", samplePWContainer[1][19][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0x170, Sequence: 19, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 14", samplePWContainer[1][20][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0xe8, Sequence: 20, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 15", samplePWContainer[1][21][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0x170, Sequence: 21, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 16", samplePWContainer[1][22][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0xe0, Sequence: 22, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 17", samplePWContainer[1][23][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0xe0, Sequence: 23, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 18", samplePWContainer[1][24][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0xe0, Sequence: 24, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 19", samplePWContainer[1][25][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0x160, Sequence: 25, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 20", samplePWContainer[1][26][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0xe0, Sequence: 26, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 21", samplePWContainer[1][27][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0x168, Sequence: 27, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 22", samplePWContainer[1][28][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0xe8, Sequence: 28, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 23", samplePWContainer[1][29][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0x178, Sequence: 29, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 24", samplePWContainer[1][30][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0x178, Sequence: 30, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 25", samplePWContainer[1][31][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0x168, Sequence: 31, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 26", samplePWContainer[1][32][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0x170, Sequence: 32, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 27", samplePWContainer[1][33][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0x178, Sequence: 33, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 28", samplePWContainer[1][34][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0x170, Sequence: 34, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 29", samplePWContainer[1][35][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0xe0, Sequence: 35, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 30", samplePWContainer[1][36][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0xe8, Sequence: 36, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 31", samplePWContainer[1][37][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0x118, Sequence: 37, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 32", samplePWContainer[1][38][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0x120, Sequence: 38, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 33", samplePWContainer[1][39][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0xd0, Sequence: 39, FileCount: 0,
}, nil},
{"PW_REGISTRY_EVENT_GLOBAL 34", samplePWContainer[1][40][0], pipewire.Header{
ID: 2,
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
Size: 0x238, Sequence: 40, FileCount: 0,
}, nil},
{"PW_SECURITY_CONTEXT_METHOD_CREATE", []byte{
// Id
3, 0, 0, 0,
// size
0xd8, 0, 0,
// opcode
1,
// seq
5, 0, 0, 0,
// n_fds
2, 0, 0, 0,
}, pipewire.Header{ID: 3, Opcode: pipewire.PW_SECURITY_CONTEXT_METHOD_CREATE,
Size: 0xd8, Sequence: 5, FileCount: 2}, nil},
{"PW_SECURITY_CONTEXT_METHOD_NUM", []byte{
// Id
0, 0, 0, 0,
// size
0x28, 0, 0,
// opcode
2,
// seq
6, 0, 0, 0,
// n_fds
0, 0, 0, 0,
}, pipewire.Header{ID: 0, Opcode: pipewire.PW_SECURITY_CONTEXT_METHOD_NUM,
Size: 0x28, Sequence: 6, FileCount: 0}, nil},
}.run(t)
t.Run("size range", func(t *testing.T) {
t.Parallel()
if _, err := (&pipewire.Header{Size: 0xff000000}).MarshalBinary(); !reflect.DeepEqual(err, pipewire.ErrSizeRange) {
t.Errorf("UnmarshalBinary: error = %v", err)
}
})
t.Run("header size", func(t *testing.T) {
t.Parallel()
if err := (*pipewire.Header)(nil).UnmarshalBinary(nil); !reflect.DeepEqual(err, pipewire.ErrBadHeader) {
t.Errorf("UnmarshalBinary: error = %v", err)
}
})
}

View File

@ -1,393 +0,0 @@
// Package pipewire provides a partial implementation of the PipeWire protocol native.
//
// This implementation is created based on black box analysis and very limited static
// analysis. The PipeWire documentation is vague and mostly nonexistent, and source code
// readability is not great due to frequent macro abuse, confusing and inconsistent naming
// schemes, almost complete absence of comments and the multiple layers of abstractions
// even internal to the library. The convoluted build system and frequent (mis)use of
// dlopen(3) further complicates static analysis efforts.
//
// Because of this, extreme care must be taken when reusing any code found in this package.
// While it is extensively tested to be correct for its role within Hakurei, remember that
// work is only done against PipeWire behaviour specific to this use case, and it is nearly
// impossible to guarantee that this interpretation of its behaviour is intended, or correct
// for any other uses of the protocol.
package pipewire
/* pipewire/device.h */
const (
PW_TYPE_INTERFACE_Device = PW_TYPE_INFO_INTERFACE_BASE + "Device"
PW_DEVICE_PERM_MASK = PW_PERM_RWXM
PW_VERSION_DEVICE = 3
)
const (
PW_DEVICE_CHANGE_MASK_PROPS = 1 << iota
PW_DEVICE_CHANGE_MASK_PARAMS
PW_DEVICE_CHANGE_MASK_ALL = 1<<iota - 1
)
const (
PW_DEVICE_EVENT_INFO = iota
PW_DEVICE_EVENT_PARAM
PW_DEVICE_EVENT_NUM
PW_VERSION_DEVICE_EVENTS = 0
)
const (
PW_DEVICE_METHOD_ADD_LISTENER = iota
PW_DEVICE_METHOD_SUBSCRIBE_PARAMS
PW_DEVICE_METHOD_ENUM_PARAMS
PW_DEVICE_METHOD_SET_PARAM
PW_DEVICE_METHOD_NUM
PW_VERSION_DEVICE_METHODS = 0
)
/* pipewire/factory.h */
const (
PW_TYPE_INTERFACE_Factory = PW_TYPE_INFO_INTERFACE_BASE + "Factory"
PW_FACTORY_PERM_MASK = PW_PERM_R | PW_PERM_M
PW_VERSION_FACTORY = 3
)
const (
PW_FACTORY_CHANGE_MASK_PROPS = 1 << iota
PW_FACTORY_CHANGE_MASK_ALL = 1<<iota - 1
)
const (
PW_FACTORY_EVENT_INFO = iota
PW_FACTORY_EVENT_NUM
PW_VERSION_FACTORY_EVENTS = 0
)
const (
PW_FACTORY_METHOD_ADD_LISTENER = iota
PW_FACTORY_METHOD_NUM
PW_VERSION_FACTORY_METHODS = 0
)
/* pipewire/link.h */
const (
PW_TYPE_INTERFACE_Link = PW_TYPE_INFO_INTERFACE_BASE + "Link"
PW_LINK_PERM_MASK = PW_PERM_R | PW_PERM_X
PW_VERSION_LINK = 3
)
const (
PW_LINK_STATE_ERROR = iota - 2 // the link is in error
PW_LINK_STATE_UNLINKED // the link is unlinked
PW_LINK_STATE_INIT // the link is initialized
PW_LINK_STATE_NEGOTIATING // the link is negotiating formats
PW_LINK_STATE_ALLOCATING // the link is allocating buffers
PW_LINK_STATE_PAUSED // the link is paused
PW_LINK_STATE_ACTIVE // the link is active
)
const (
PW_LINK_CHANGE_MASK_STATE = (1 << iota)
PW_LINK_CHANGE_MASK_FORMAT
PW_LINK_CHANGE_MASK_PROPS
PW_LINK_CHANGE_MASK_ALL = 1<<iota - 1
)
const (
PW_LINK_EVENT_INFO = iota
PW_LINK_EVENT_NUM
PW_VERSION_LINK_EVENTS = 0
)
const (
PW_LINK_METHOD_ADD_LISTENER = iota
PW_LINK_METHOD_NUM
PW_VERSION_LINK_METHODS = 0
)
/* pipewire/module.h */
const (
PW_TYPE_INTERFACE_Module = PW_TYPE_INFO_INTERFACE_BASE + "Module"
PW_MODULE_PERM_MASK = PW_PERM_R | PW_PERM_M
PW_VERSION_MODULE = 3
)
const (
PW_MODULE_CHANGE_MASK_PROPS = 1 << iota
PW_MODULE_CHANGE_MASK_ALL = 1<<iota - 1
)
const (
PW_MODULE_EVENT_INFO = iota
PW_MODULE_EVENT_NUM
PW_VERSION_MODULE_EVENTS = 0
)
const (
PW_MODULE_METHOD_ADD_LISTENER = iota
PW_MODULE_METHOD_NUM
PW_VERSION_MODULE_METHODS = 0
)
/* pipewire/impl-module.h */
const (
PIPEWIRE_SYMBOL_MODULE_INIT = "pipewire__module_init"
PIPEWIRE_MODULE_PREFIX = "libpipewire-"
PW_VERSION_IMPL_MODULE_EVENTS = 0
)
/* pipewire/node.h */
const (
PW_TYPE_INTERFACE_Node = PW_TYPE_INFO_INTERFACE_BASE + "Node"
PW_NODE_PERM_MASK = PW_PERM_RWXML
PW_VERSION_NODE = 3
)
const (
PW_NODE_STATE_ERROR = iota - 1 // error state
PW_NODE_STATE_CREATING // the node is being created
PW_NODE_STATE_SUSPENDED // the node is suspended, the device might be closed
PW_NODE_STATE_IDLE // the node is running but there is no active port
PW_NODE_STATE_RUNNING // the node is running
)
const (
PW_NODE_CHANGE_MASK_INPUT_PORTS = 1 << iota
PW_NODE_CHANGE_MASK_OUTPUT_PORTS
PW_NODE_CHANGE_MASK_STATE
PW_NODE_CHANGE_MASK_PROPS
PW_NODE_CHANGE_MASK_PARAMS
PW_NODE_CHANGE_MASK_ALL = 1<<iota - 1
)
const (
PW_NODE_EVENT_INFO = iota
PW_NODE_EVENT_PARAM
PW_NODE_EVENT_NUM
PW_VERSION_NODE_EVENTS = 0
)
const (
PW_NODE_METHOD_ADD_LISTENER = iota
PW_NODE_METHOD_SUBSCRIBE_PARAMS
PW_NODE_METHOD_ENUM_PARAMS
PW_NODE_METHOD_SET_PARAM
PW_NODE_METHOD_SEND_COMMAND
PW_NODE_METHOD_NUM
PW_VERSION_NODE_METHODS = 0
)
/* pipewire/permission.h */
const (
PW_PERM_R = 0400 // object can be seen and events can be received
PW_PERM_W = 0200 // methods can be called that modify the object
PW_PERM_X = 0100 // methods can be called on the object. The W flag must be present in order to call methods that modify the object.
PW_PERM_M = 0010 // metadata can be set on object, Since 0.3.9
PW_PERM_L = 0020 // a link can be made between a node that doesn't have permission to see the other node, Since 0.3.77
PW_PERM_RW = PW_PERM_R | PW_PERM_W
PW_PERM_RWX = PW_PERM_RW | PW_PERM_X
PW_PERM_RWXM = PW_PERM_RWX | PW_PERM_M
PW_PERM_RWXML = PW_PERM_RWXM | PW_PERM_L
PW_PERM_ALL = PW_PERM_RWXM
PW_PERM_INVALID Word = 0xffffffff
)
/* pipewire/port.h */
const (
PW_TYPE_INTERFACE_Port = PW_TYPE_INFO_INTERFACE_BASE + "Port"
PW_PORT_PERM_MASK = PW_PERM_R | PW_PERM_X | PW_PERM_M
PW_VERSION_PORT = 3
)
const (
PW_PORT_CHANGE_MASK_PROPS = 1 << iota
PW_PORT_CHANGE_MASK_PARAMS
PW_PORT_CHANGE_MASK_ALL = 1<<iota - 1
)
const (
PW_PORT_EVENT_INFO = iota
PW_PORT_EVENT_PARAM
PW_PORT_EVENT_NUM
PW_VERSION_PORT_EVENTS = 0
)
const (
PW_PORT_METHOD_ADD_LISTENER = iota
PW_PORT_METHOD_SUBSCRIBE_PARAMS
PW_PORT_METHOD_ENUM_PARAMS
PW_PORT_METHOD_NUM
PW_VERSION_PORT_METHODS = 0
)
/* pipewire/extensions/client-node.h */
const (
PW_TYPE_INTERFACE_ClientNode = PW_TYPE_INFO_INTERFACE_BASE + "ClientNode"
PW_VERSION_CLIENT_NODE = 6
PW_EXTENSION_MODULE_CLIENT_NODE = PIPEWIRE_MODULE_PREFIX + "module-client-node"
)
const (
PW_CLIENT_NODE_EVENT_TRANSPORT = iota
PW_CLIENT_NODE_EVENT_SET_PARAM
PW_CLIENT_NODE_EVENT_SET_IO
PW_CLIENT_NODE_EVENT_EVENT
PW_CLIENT_NODE_EVENT_COMMAND
PW_CLIENT_NODE_EVENT_ADD_PORT
PW_CLIENT_NODE_EVENT_REMOVE_PORT
PW_CLIENT_NODE_EVENT_PORT_SET_PARAM
PW_CLIENT_NODE_EVENT_PORT_USE_BUFFERS
PW_CLIENT_NODE_EVENT_PORT_SET_IO
PW_CLIENT_NODE_EVENT_SET_ACTIVATION
PW_CLIENT_NODE_EVENT_PORT_SET_MIX_INFO
PW_CLIENT_NODE_EVENT_NUM
PW_VERSION_CLIENT_NODE_EVENTS = 1
)
const (
PW_CLIENT_NODE_METHOD_ADD_LISTENER = iota
PW_CLIENT_NODE_METHOD_GET_NODE
PW_CLIENT_NODE_METHOD_UPDATE
PW_CLIENT_NODE_METHOD_PORT_UPDATE
PW_CLIENT_NODE_METHOD_SET_ACTIVE
PW_CLIENT_NODE_METHOD_EVENT
PW_CLIENT_NODE_METHOD_PORT_BUFFERS
PW_CLIENT_NODE_METHOD_NUM
PW_VERSION_CLIENT_NODE_METHODS = 0
)
const (
PW_CLIENT_NODE_UPDATE_PARAMS = 1 << iota
PW_CLIENT_NODE_UPDATE_INFO
)
const (
PW_CLIENT_NODE_PORT_UPDATE_PARAMS = 1 << iota
PW_CLIENT_NODE_PORT_UPDATE_INFO
)
/* pipewire/extensions/metadata.h */
const (
PW_TYPE_INTERFACE_Metadata = PW_TYPE_INFO_INTERFACE_BASE + "Metadata"
PW_METADATA_PERM_MASK = PW_PERM_RWX
PW_VERSION_METADATA = 3
PW_EXTENSION_MODULE_METADATA = PIPEWIRE_MODULE_PREFIX + "module-metadata"
)
const (
PW_METADATA_EVENT_PROPERTY = iota
PW_METADATA_EVENT_NUM
PW_VERSION_METADATA_EVENTS = 0
)
const (
PW_METADATA_METHOD_ADD_LISTENER = iota
PW_METADATA_METHOD_SET_PROPERTY
PW_METADATA_METHOD_CLEAR
PW_METADATA_METHOD_NUM
PW_VERSION_METADATA_METHODS = 0
)
const (
PW_KEY_METADATA_NAME = "metadata.name"
PW_KEY_METADATA_VALUES = "metadata.values"
)
/* pipewire/extensions/profiler.h */
const (
PW_TYPE_INTERFACE_Profiler = PW_TYPE_INFO_INTERFACE_BASE + "Profiler"
PW_VERSION_PROFILER = 3
PW_PROFILER_PERM_MASK = PW_PERM_R
PW_EXTENSION_MODULE_PROFILER = PIPEWIRE_MODULE_PREFIX + "module-profiler"
)
const (
PW_PROFILER_EVENT_PROFILE = iota
PW_PROFILER_EVENT_NUM
PW_VERSION_PROFILER_EVENTS = 0
)
const (
PW_PROFILER_METHOD_ADD_LISTENER = iota
PW_PROFILER_METHOD_NUM
PW_VERSION_PROFILER_METHODS = 0
)
const (
PW_KEY_PROFILER_NAME = "profiler.name"
)
/* pipewire/extensions/security-context.h */
const (
PW_TYPE_INTERFACE_SecurityContext = PW_TYPE_INFO_INTERFACE_BASE + "SecurityContext"
PW_SECURITY_CONTEXT_PERM_MASK = PW_PERM_RWX
PW_VERSION_SECURITY_CONTEXT = 3
PW_EXTENSION_MODULE_SECURITY_CONTEXT = PIPEWIRE_MODULE_PREFIX + "module-security-context"
)
const (
PW_SECURITY_CONTEXT_EVENT_NUM = iota
PW_VERSION_SECURITY_CONTEXT_EVENTS = 0
)
const (
PW_SECURITY_CONTEXT_METHOD_ADD_LISTENER = iota
PW_SECURITY_CONTEXT_METHOD_CREATE
PW_SECURITY_CONTEXT_METHOD_NUM
PW_VERSION_SECURITY_CONTEXT_METHODS = 0
)
/* pipewire/type.h */
const (
PW_TYPE_INFO_BASE = "PipeWire:"
PW_TYPE_INFO_Object = PW_TYPE_INFO_BASE + "Object"
PW_TYPE_INFO_OBJECT_BASE = PW_TYPE_INFO_Object + ":"
PW_TYPE_INFO_Interface = PW_TYPE_INFO_BASE + "Interface"
PW_TYPE_INFO_INTERFACE_BASE = PW_TYPE_INFO_Interface + ":"
)

View File

@ -1,59 +0,0 @@
package pipewire_test
import (
_ "embed"
"encoding/binary"
"hakurei.app/internal/pipewire"
)
var (
//go:embed testdata/pw-container-00-sendmsg
samplePWContainer00 string
//go:embed testdata/pw-container-01-recvmsg
samplePWContainer01 string
//go:embed testdata/pw-container-03-sendmsg
samplePWContainer03 string
//go:embed testdata/pw-container-04-recvmsg
samplePWContainer04 string
//go:embed testdata/pw-container-06-sendmsg
samplePWContainer06 string
//go:embed testdata/pw-container-07-recvmsg
samplePWContainer07 string
// samplePWContainer is a collection of messages from the pw-container sample.
samplePWContainer = [...][][3][]byte{
splitMessages(samplePWContainer00),
splitMessages(samplePWContainer01),
nil,
splitMessages(samplePWContainer03),
splitMessages(samplePWContainer04),
nil,
splitMessages(samplePWContainer06),
splitMessages(samplePWContainer07),
nil,
}
)
// splitMessages splits concatenated messages into groups of
// header, payload, footer of each individual message.
// splitMessages panics on any decoding error.
func splitMessages(iovec string) (messages [][3][]byte) {
data := []byte(iovec)
messages = make([][3][]byte, 0, 1<<7)
var header pipewire.Header
for len(data) != 0 {
if err := header.UnmarshalBinary(data[:pipewire.SizeHeader]); err != nil {
panic(err)
}
size := pipewire.SizePrefix + binary.NativeEndian.Uint32(data[pipewire.SizeHeader:])
messages = append(messages, [3][]byte{
data[:pipewire.SizeHeader],
data[pipewire.SizeHeader : pipewire.SizeHeader+size],
data[pipewire.SizeHeader+size : pipewire.SizeHeader+header.Size],
})
data = data[pipewire.SizeHeader+header.Size:]
}
return
}

View File

@ -1,621 +0,0 @@
package pipewire
import (
"encoding/binary"
"io"
"math"
"reflect"
"strconv"
)
type (
// A Word is a 32-bit unsigned integer.
//
// Values internal to a message appear to always be aligned to 32-bit boundary.
Word = uint32
// A Bool is a boolean value representing SPA_TYPE_Bool.
Bool = bool
// An Id is an enumerated value representing SPA_TYPE_Id.
Id = Word
// An Int is a signed integer value representing SPA_TYPE_Int.
Int = int32
// A Long is a signed integer value representing SPA_TYPE_Long.
Long = int64
// A Float is a floating point value representing SPA_TYPE_Float.
Float = float32
// A Double is a floating point value representing SPA_TYPE_Double.
Double = float64
// A String is a string value representing SPA_TYPE_String.
String = string
// Bytes is a byte slice representing SPA_TYPE_Bytes.
Bytes = []byte
)
const (
// SizeAlign is the boundary which POD starts are always aligned to.
SizeAlign = 8
// SizeSPrefix is the fixed, unpadded size of the fixed-size prefix encoding POD wire size.
SizeSPrefix = 4
// SizeTPrefix is the fixed, unpadded size of the fixed-size prefix encoding POD value type.
SizeTPrefix = 4
// SizePrefix is the fixed, unpadded size of the fixed-size POD prefix.
SizePrefix = SizeSPrefix + SizeTPrefix
// SizeId is the fixed, unpadded size of a [SPA_TYPE_Id] value.
SizeId Word = 4
// SizeInt is the fixed, unpadded size of a [SPA_TYPE_Int] value.
SizeInt Word = 4
// SizeLong is the fixed, unpadded size of a [SPA_TYPE_Long] value.
SizeLong Word = 8
)
/* Basic types */
const (
/* POD's can contain a number of basic SPA types: */
SPA_TYPE_START = 0x00000 + iota
SPA_TYPE_None // No value or a NULL pointer.
SPA_TYPE_Bool // A boolean value.
SPA_TYPE_Id // An enumerated value.
SPA_TYPE_Int // An integer value, 32-bit.
SPA_TYPE_Long // An integer value, 64-bit.
SPA_TYPE_Float // A floating point value, 32-bit.
SPA_TYPE_Double // A floating point value, 64-bit.
SPA_TYPE_String // A string.
SPA_TYPE_Bytes // A byte array.
SPA_TYPE_Rectangle // A rectangle with width and height.
SPA_TYPE_Fraction // A fraction with numerator and denominator.
SPA_TYPE_Bitmap // An array of bits.
/* POD's can be grouped together in these container types: */
SPA_TYPE_Array // An array of equal sized objects.
SPA_TYPE_Struct // A collection of types and objects.
SPA_TYPE_Object // An object with properties.
SPA_TYPE_Sequence // A timed sequence of POD's.
/* POD's can also contain some extra types: */
SPA_TYPE_Pointer // A typed pointer in memory.
SPA_TYPE_Fd // A file descriptor.
SPA_TYPE_Choice // A choice of values.
SPA_TYPE_Pod // A generic type for the POD itself.
_SPA_TYPE_LAST // not part of ABI
)
// A KnownSize value has known POD encoded size before serialisation.
type KnownSize interface {
// Size returns the POD encoded size of the receiver.
Size() Word
}
// PaddingSize returns the padding size corresponding to a wire size.
func PaddingSize[W Word | int](wireSize W) W { return (SizeAlign - (wireSize)%SizeAlign) % SizeAlign }
// PaddedSize returns the padded size corresponding to a wire size.
func PaddedSize[W Word | int](wireSize W) W { return wireSize + PaddingSize(wireSize) }
// Size returns prefixed and padded size corresponding to a wire size.
func Size[W Word | int](wireSize W) W { return SizePrefix + PaddedSize(wireSize) }
// SizeString returns prefixed and padded size corresponding to a string.
func SizeString[W Word | int](s string) W { return Size(W(len(s)) + 1) }
// PODMarshaler is the interface implemented by an object that can
// marshal itself into PipeWire POD encoding.
type PODMarshaler interface {
// MarshalPOD encodes the receiver into PipeWire POD encoding,
// appends it to data, and returns the result.
MarshalPOD(data []byte) ([]byte, error)
}
// An UnsupportedTypeError is returned by [Marshal] when attempting
// to encode an unsupported value type.
type UnsupportedTypeError struct{ Type reflect.Type }
func (e *UnsupportedTypeError) Error() string { return "unsupported type: " + e.Type.String() }
// An UnsupportedSizeError is returned by [Marshal] when attempting
// to encode a value with its encoded size exceeding what could be
// represented by the format.
type UnsupportedSizeError int
func (e UnsupportedSizeError) Error() string { return "size out of range: " + strconv.Itoa(int(e)) }
// Marshal returns the PipeWire POD encoding of v.
func Marshal(v any) ([]byte, error) {
var data []byte
if s, ok := v.(KnownSize); ok {
data = make([]byte, 0, s.Size())
}
return MarshalAppend(data, v)
}
// MarshalAppend appends the PipeWire POD encoding of v to data.
func MarshalAppend(data []byte, v any) ([]byte, error) {
return marshalValueAppend(data, reflect.ValueOf(v))
}
// appendInner calls f and handles size prefix and padding around the appended data.
// f must only append to data.
func appendInner(data []byte, f func(data []byte) ([]byte, error)) ([]byte, error) {
data = append(data, make([]byte, SizeSPrefix)...)
rData, err := f(data)
if err != nil {
return data, err
}
size := len(rData) - len(data) + SizeSPrefix
// compensated for size and type prefix
wireSize := size - SizePrefix
if wireSize > math.MaxUint32 {
return data, UnsupportedSizeError(wireSize)
}
binary.NativeEndian.PutUint32(rData[len(data)-SizeSPrefix:len(data)], Word(wireSize))
rData = append(rData, make([]byte, PaddingSize(size))...)
return rData, nil
}
// marshalValueAppendRaw implements [MarshalAppend] on [reflect.Value].
func marshalValueAppend(data []byte, v reflect.Value) ([]byte, error) {
if v.CanInterface() && (v.Kind() != reflect.Pointer || !v.IsNil()) {
if m, ok := v.Interface().(PODMarshaler); ok {
var err error
data, err = m.MarshalPOD(data)
return data, err
}
}
return appendInner(data, func(data []byte) ([]byte, error) { return marshalValueAppendRaw(data, v) })
}
// marshalValueAppendRaw implements [MarshalAppend] on [reflect.Value] without the size prefix.
func marshalValueAppendRaw(data []byte, v reflect.Value) ([]byte, error) {
switch v.Kind() {
case reflect.Uint32:
data = binary.NativeEndian.AppendUint32(data, SPA_TYPE_Id)
data = binary.NativeEndian.AppendUint32(data, Word(v.Uint()))
return data, nil
case reflect.Int32:
data = binary.NativeEndian.AppendUint32(data, SPA_TYPE_Int)
data = binary.NativeEndian.AppendUint32(data, Word(v.Int()))
return data, nil
case reflect.Int64:
data = binary.NativeEndian.AppendUint32(data, SPA_TYPE_Long)
data = binary.NativeEndian.AppendUint64(data, uint64(v.Int()))
return data, nil
case reflect.Struct:
data = binary.NativeEndian.AppendUint32(data, SPA_TYPE_Struct)
var err error
for i := 0; i < v.NumField(); i++ {
data, err = marshalValueAppend(data, v.Field(i))
if err != nil {
return data, err
}
}
return data, nil
case reflect.Pointer:
if v.IsNil() {
data = binary.NativeEndian.AppendUint32(data, SPA_TYPE_None)
return data, nil
}
return marshalValueAppendRaw(data, v.Elem())
case reflect.String:
data = binary.NativeEndian.AppendUint32(data, SPA_TYPE_String)
data = append(data, []byte(v.String())...)
data = append(data, 0)
return data, nil
default:
return data, &UnsupportedTypeError{v.Type()}
}
}
// PODUnmarshaler is the interface implemented by an object that can
// unmarshal a PipeWire POD encoding representation of itself.
type PODUnmarshaler interface {
// UnmarshalPOD must be able to decode the form generated by MarshalPOD.
// UnmarshalPOD must copy the data if it wishes to retain the data
// after returning.
UnmarshalPOD(data []byte) (Word, error)
}
// An InvalidUnmarshalError describes an invalid argument passed to [Unmarshal].
// (The argument to [Unmarshal] must be a non-nil pointer.)
type InvalidUnmarshalError struct{ Type reflect.Type }
func (e *InvalidUnmarshalError) Error() string {
if e.Type == nil {
return "attempting to unmarshal to nil"
}
if e.Type.Kind() != reflect.Pointer {
return "attempting to unmarshal to non-pointer type: " + e.Type.String()
}
return "attempting to unmarshal to nil " + e.Type.String()
}
// Unmarshal parses the PipeWire POD encoded data and stores the result
// in the value pointed to by v. If v is nil or not a pointer,
// Unmarshal returns an [InvalidUnmarshalError].
func Unmarshal(data []byte, v any) error {
if n, err := UnmarshalNext(data, v); err != nil {
return err
} else if len(data) > int(n) {
return &TrailingGarbageError{data[int(n):]}
}
return nil
}
// UnmarshalNext implements [Unmarshal] but returns the size of message decoded
// and skips the final trailing garbage check.
func UnmarshalNext(data []byte, v any) (size Word, err error) {
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Pointer || rv.IsNil() {
return 0, &InvalidUnmarshalError{reflect.TypeOf(v)}
}
err = unmarshalValue(data, rv.Elem(), &size)
// prefix and padding size
size = Size(size)
return
}
// UnmarshalSetError describes a value that cannot be set during [Unmarshal].
// This is likely an unexported struct field.
type UnmarshalSetError struct{ Type reflect.Type }
func (u *UnmarshalSetError) Error() string { return "cannot set: " + u.Type.String() }
// A TrailingGarbageError describes extra bytes after decoding
// has completed during [Unmarshal].
type TrailingGarbageError struct{ Data []byte }
func (e *TrailingGarbageError) Error() string {
if len(e.Data) < SizePrefix {
return "got " + strconv.Itoa(len(e.Data)) + " bytes of trailing garbage"
}
return "data has extra values starting with type " + strconv.Itoa(int(binary.NativeEndian.Uint32(e.Data[SizeSPrefix:])))
}
// A StringTerminationError describes an incorrectly terminated string
// encountered during [Unmarshal].
type StringTerminationError struct{ Value byte }
func (e StringTerminationError) Error() string {
return "got byte " + strconv.Itoa(int(e.Value)) + " instead of NUL"
}
// unmarshalValue implements [Unmarshal] on [reflect.Value] without compensating for prefix and padding size.
func unmarshalValue(data []byte, v reflect.Value, wireSizeP *Word) error {
if !v.CanSet() {
return &UnmarshalSetError{v.Type()}
}
if v.CanInterface() {
if v.Kind() == reflect.Pointer {
v.Set(reflect.New(v.Type().Elem()))
}
if u, ok := v.Interface().(PODUnmarshaler); ok {
var err error
*wireSizeP, err = u.UnmarshalPOD(data)
return err
}
}
switch v.Kind() {
case reflect.Uint32:
*wireSizeP = SizeId
if err := unmarshalCheckTypeBounds(&data, SPA_TYPE_Id, wireSizeP); err != nil {
return err
}
v.SetUint(uint64(binary.NativeEndian.Uint32(data)))
return nil
case reflect.Int32:
*wireSizeP = SizeInt
if err := unmarshalCheckTypeBounds(&data, SPA_TYPE_Int, wireSizeP); err != nil {
return err
}
v.SetInt(int64(binary.NativeEndian.Uint32(data)))
return nil
case reflect.Int64:
*wireSizeP = SizeLong
if err := unmarshalCheckTypeBounds(&data, SPA_TYPE_Long, wireSizeP); err != nil {
return err
}
v.SetInt(int64(binary.NativeEndian.Uint64(data)))
return nil
case reflect.Struct:
*wireSizeP = 0
if err := unmarshalCheckTypeBounds(&data, SPA_TYPE_Struct, wireSizeP); err != nil {
return err
}
var fieldWireSize Word
for i := 0; i < v.NumField(); i++ {
if err := unmarshalValue(data, v.Field(i), &fieldWireSize); err != nil {
return err
}
// bounds check completed in successful call to unmarshalValue
data = data[Size(fieldWireSize):]
}
if len(data) != 0 {
return &TrailingGarbageError{data}
}
return nil
case reflect.Pointer:
if len(data) < SizePrefix {
return io.ErrUnexpectedEOF
}
switch binary.NativeEndian.Uint32(data[SizeSPrefix:]) {
case SPA_TYPE_None:
v.SetZero()
return nil
default:
v.Set(reflect.New(v.Type().Elem()))
return unmarshalValue(data, v.Elem(), wireSizeP)
}
case reflect.String:
*wireSizeP = 0
if err := unmarshalCheckTypeBounds(&data, SPA_TYPE_String, wireSizeP); err != nil {
return err
}
// string size, one extra NUL byte
size := int(*wireSizeP)
if len(data) < size {
return io.ErrUnexpectedEOF
}
// the serialised strings still include NUL termination
if data[size-1] != 0 {
return StringTerminationError{data[size-1]}
}
v.SetString(string(data[:size-1]))
return nil
default:
return &UnsupportedTypeError{v.Type()}
}
}
// An InconsistentSizeError describes an inconsistent size prefix encountered
// in data passed to [Unmarshal].
type InconsistentSizeError struct{ Prefix, Expect Word }
func (e *InconsistentSizeError) Error() string {
return "unexpected size prefix: " + strconv.Itoa(int(e.Prefix)) + ", want " + strconv.Itoa(int(e.Expect))
}
// An UnexpectedTypeError describes an unexpected type encountered
// in data passed to [Unmarshal].
type UnexpectedTypeError struct{ Type, Expect Word }
func (u *UnexpectedTypeError) Error() string {
return "unexpected type: " + strconv.Itoa(int(u.Type)) + ", want " + strconv.Itoa(int(u.Expect))
}
// unmarshalCheckTypeBounds performs bounds checks on data and validates the type and size prefixes.
// An expected size of zero skips further bounds checks.
func unmarshalCheckTypeBounds(data *[]byte, t Word, sizeP *Word) error {
if len(*data) < SizePrefix {
return io.ErrUnexpectedEOF
}
wantSize := *sizeP
gotSize := binary.NativeEndian.Uint32(*data)
*sizeP = gotSize
if wantSize != 0 && gotSize != wantSize {
return &InconsistentSizeError{gotSize, wantSize}
}
if len(*data)-SizePrefix < int(gotSize) {
return io.ErrUnexpectedEOF
}
gotType := binary.NativeEndian.Uint32((*data)[SizeSPrefix:])
if gotType != t {
return &UnexpectedTypeError{gotType, t}
}
*data = (*data)[SizePrefix : gotSize+SizePrefix]
return nil
}
// The Footer contains additional messages, not directed to
// the destination object defined by the Id field.
type Footer[T any] struct {
// The footer opcode.
Opcode Id `json:"opcode"`
// The footer payload struct.
Payload T `json:"payload"`
}
// MarshalBinary satisfies [encoding.BinaryMarshaler] via [Marshal].
func (f *Footer[T]) MarshalBinary() ([]byte, error) { return Marshal(f) }
// UnmarshalBinary satisfies [encoding.BinaryUnmarshaler] via [Unmarshal].
func (f *Footer[T]) UnmarshalBinary(data []byte) error { return Unmarshal(data, f) }
// SPADictItem represents spa_dict_item.
type SPADictItem struct{ Key, Value string }
// SPADict represents spa_dict.
type SPADict []SPADictItem
// Size satisfies [KnownSize] with a value computed at runtime.
func (d *SPADict) Size() Word {
if d == nil {
return 0
}
// struct prefix, NItems value
size := SizePrefix + int(Size(SizeInt))
for i := range *d {
size += SizeString[int]((*d)[i].Key)
size += SizeString[int]((*d)[i].Value)
}
return Word(size)
}
// MarshalPOD satisfies [PODMarshaler] as [SPADict] violates the POD type system.
func (d *SPADict) MarshalPOD(data []byte) ([]byte, error) {
return appendInner(data, func(dataPrefix []byte) (data []byte, err error) {
data = binary.NativeEndian.AppendUint32(dataPrefix, SPA_TYPE_Struct)
if data, err = MarshalAppend(data, Int(len(*d))); err != nil {
return
}
for i := range *d {
if data, err = MarshalAppend(data, (*d)[i].Key); err != nil {
return
}
if data, err = MarshalAppend(data, (*d)[i].Value); err != nil {
return
}
}
return
})
}
// UnmarshalPOD satisfies [PODUnmarshaler] as [SPADict] violates the POD type system.
func (d *SPADict) UnmarshalPOD(data []byte) (Word, error) {
var wireSize Word
if err := unmarshalCheckTypeBounds(&data, SPA_TYPE_Struct, &wireSize); err != nil {
return wireSize, err
}
// bounds check completed in successful call to unmarshalCheckTypeBounds
data = data[:wireSize]
var count Int
if size, err := UnmarshalNext(data, &count); err != nil {
return wireSize, err
} else {
// bounds check completed in successful call to Unmarshal
data = data[size:]
}
*d = make([]SPADictItem, count)
for i := range *d {
if size, err := UnmarshalNext(data, &(*d)[i].Key); err != nil {
return wireSize, err
} else {
// bounds check completed in successful call to Unmarshal
data = data[size:]
}
if size, err := UnmarshalNext(data, &(*d)[i].Value); err != nil {
return wireSize, err
} else {
// bounds check completed in successful call to Unmarshal
data = data[size:]
}
}
if len(data) != 0 {
return wireSize, &TrailingGarbageError{data}
}
return wireSize, nil
}
/* Pointers */
const (
SPA_TYPE_POINTER_START = 0x10000 + iota
SPA_TYPE_POINTER_Buffer
SPA_TYPE_POINTER_Meta
SPA_TYPE_POINTER_Dict
_SPA_TYPE_POINTER_LAST // not part of ABI
)
/* Events */
const (
SPA_TYPE_EVENT_START = 0x20000 + iota
SPA_TYPE_EVENT_Device
SPA_TYPE_EVENT_Node
_SPA_TYPE_EVENT_LAST // not part of ABI
)
/* Commands */
const (
SPA_TYPE_COMMAND_START = 0x30000 + iota
SPA_TYPE_COMMAND_Device
SPA_TYPE_COMMAND_Node
_SPA_TYPE_COMMAND_LAST // not part of ABI
)
/* Objects */
const (
SPA_TYPE_OBJECT_START = 0x40000 + iota
SPA_TYPE_OBJECT_PropInfo
SPA_TYPE_OBJECT_Props
SPA_TYPE_OBJECT_Format
SPA_TYPE_OBJECT_ParamBuffers
SPA_TYPE_OBJECT_ParamMeta
SPA_TYPE_OBJECT_ParamIO
SPA_TYPE_OBJECT_ParamProfile
SPA_TYPE_OBJECT_ParamPortConfig
SPA_TYPE_OBJECT_ParamRoute
SPA_TYPE_OBJECT_Profiler
SPA_TYPE_OBJECT_ParamLatency
SPA_TYPE_OBJECT_ParamProcessLatency
SPA_TYPE_OBJECT_ParamTag
_SPA_TYPE_OBJECT_LAST // not part of ABI
)
/* vendor extensions */
const (
SPA_TYPE_VENDOR_PipeWire = 0x02000000
SPA_TYPE_VENDOR_Other = 0x7f000000
)
const (
SPA_TYPE_INFO_BASE = "Spa:"
SPA_TYPE_INFO_Flags = SPA_TYPE_INFO_BASE + "Flags"
SPA_TYPE_INFO_FLAGS_BASE = SPA_TYPE_INFO_Flags + ":"
SPA_TYPE_INFO_Enum = SPA_TYPE_INFO_BASE + "Enum"
SPA_TYPE_INFO_ENUM_BASE = SPA_TYPE_INFO_Enum + ":"
SPA_TYPE_INFO_Pod = SPA_TYPE_INFO_BASE + "Pod"
SPA_TYPE_INFO_POD_BASE = SPA_TYPE_INFO_Pod + ":"
SPA_TYPE_INFO_Struct = SPA_TYPE_INFO_POD_BASE + "Struct"
SPA_TYPE_INFO_STRUCT_BASE = SPA_TYPE_INFO_Struct + ":"
SPA_TYPE_INFO_Object = SPA_TYPE_INFO_POD_BASE + "Object"
SPA_TYPE_INFO_OBJECT_BASE = SPA_TYPE_INFO_Object + ":"
SPA_TYPE_INFO_Pointer = SPA_TYPE_INFO_BASE + "Pointer"
SPA_TYPE_INFO_POINTER_BASE = SPA_TYPE_INFO_Pointer + ":"
SPA_TYPE_INFO_Interface = SPA_TYPE_INFO_POINTER_BASE + "Interface"
SPA_TYPE_INFO_INTERFACE_BASE = SPA_TYPE_INFO_Interface + ":"
SPA_TYPE_INFO_Event = SPA_TYPE_INFO_OBJECT_BASE + "Event"
SPA_TYPE_INFO_EVENT_BASE = SPA_TYPE_INFO_Event + ":"
SPA_TYPE_INFO_Command = SPA_TYPE_INFO_OBJECT_BASE + "Command"
SPA_TYPE_INFO_COMMAND_BASE = SPA_TYPE_INFO_Command + ":"
)

View File

@ -1,76 +0,0 @@
package pipewire_test
import (
"encoding"
"encoding/json"
"reflect"
"testing"
"hakurei.app/internal/pipewire"
)
type encodingTestCases[V any, S interface {
encoding.BinaryMarshaler
encoding.BinaryUnmarshaler
*V
}] []struct {
// Uninterpreted name of subtest.
name string
// Encoded data.
wantData []byte
// Value corresponding to wantData.
value V
// Expected decoding error. Skips encoding check if non-nil.
wantErr error
}
// run runs all test cases as subtests of [testing.T].
func (testCases encodingTestCases[V, S]) run(t *testing.T) {
t.Helper()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
t.Run("decode", func(t *testing.T) {
t.Parallel()
var value V
if err := S(&value).UnmarshalBinary(tc.wantData); err != nil {
t.Fatalf("UnmarshalBinary: error = %v", err)
}
if !reflect.DeepEqual(&value, &tc.value) {
t.Fatalf("UnmarshalBinary:\n%s\nwant\n%s", mustMarshalJSON(value), mustMarshalJSON(tc.value))
}
})
t.Run("encode", func(t *testing.T) {
t.Parallel()
if gotData, err := S(&tc.value).MarshalBinary(); err != nil {
t.Fatalf("MarshalBinary: error = %v", err)
} else if string(gotData) != string(tc.wantData) {
t.Fatalf("MarshalBinary: %#v, want %#v", gotData, tc.wantData)
}
})
if s, ok := any(&tc.value).(pipewire.KnownSize); ok {
t.Run("size", func(t *testing.T) {
if got := int(s.Size()); got != len(tc.wantData) {
t.Errorf("Size: %d, want %d", got, len(tc.wantData))
}
})
}
})
}
}
// mustMarshalJSON calls [json.Marshal] and returns the result.
func mustMarshalJSON(v any) string {
if data, err := json.Marshal(v); err != nil {
panic(err)
} else {
return string(data)
}
}

View File

@ -3,6 +3,7 @@ package system
import (
"errors"
"fmt"
"os"
"hakurei.app/container/check"
"hakurei.app/hst"
@ -62,6 +63,9 @@ func (w *waylandOp) revert(sys *I, _ *Criteria) error {
if w.ctx != nil {
hangupErr = w.ctx.Close()
}
if err := sys.remove(w.dst.String()); err != nil && !errors.Is(err, os.ErrNotExist) {
removeErr = err
}
return newOpError("wayland", errors.Join(hangupErr, removeErr), true)
}

View File

@ -36,6 +36,21 @@ func TestWaylandOp(t *testing.T) {
call("aclUpdate", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", 0xbeef, []acl.Perm{acl.Read, acl.Write, acl.Execute}}, nil, stub.UniqueError(2)),
}, &OpError{Op: "wayland", Err: errors.Join(stub.UniqueError(2), os.ErrInvalid)}, nil, nil},
{"remove", 0xbeef, 0xff, &waylandOp{nil,
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
m("/run/user/1971/wayland-0"),
"org.chromium.Chromium",
"ebf083d1b175911782d413369b64ce7c",
}, []stub.Call{
call("waylandNew", stub.ExpectArgs{m("/run/user/1971/wayland-0"), m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"), "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c"}, nil, nil),
call("verbosef", stub.ExpectArgs{"wayland pathname socket on %q via %q", []any{m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"), m("/run/user/1971/wayland-0")}}, nil, nil),
call("chmod", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", os.FileMode(0)}, nil, nil),
call("aclUpdate", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", 0xbeef, []acl.Perm{acl.Read, acl.Write, acl.Execute}}, nil, nil),
}, nil, []stub.Call{
call("verbosef", stub.ExpectArgs{"hanging up wayland socket on %q", []any{m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland")}}, nil, nil),
call("remove", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"}, nil, stub.UniqueError(1)),
}, &OpError{Op: "wayland", Err: errors.Join(stub.UniqueError(1)), Revert: true}},
{"success", 0xbeef, 0xff, &waylandOp{nil,
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
m("/run/user/1971/wayland-0"),
@ -48,6 +63,7 @@ func TestWaylandOp(t *testing.T) {
call("aclUpdate", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", 0xbeef, []acl.Perm{acl.Read, acl.Write, acl.Execute}}, nil, nil),
}, nil, []stub.Call{
call("verbosef", stub.ExpectArgs{"hanging up wayland socket on %q", []any{m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland")}}, nil, nil),
call("remove", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"}, nil, nil),
}, nil},
})

View File

@ -12,32 +12,18 @@ import (
type SecurityContext struct {
// Pipe with its write end passed to security-context-v1.
closeFds [2]int
// Absolute pathname the socket was bound to.
bindPath *check.Absolute
}
// Close releases any resources held by [SecurityContext], and prevents further
// connections to its associated socket.
//
// A non-nil error has the concrete type [Error].
func (sc *SecurityContext) Close() error {
if sc == nil || sc.bindPath == nil {
if sc == nil {
return os.ErrInvalid
}
e := Error{RCleanup, sc.bindPath.String(), "", errors.Join(
return errors.Join(
syscall.Close(sc.closeFds[1]),
syscall.Close(sc.closeFds[0]),
// there is still technically a TOCTOU here but this is internal
// and has access to the privileged wayland socket, so it only
// receives trusted input (e.g. from cmd/hakurei) anyway
os.Remove(sc.bindPath.String()),
)}
if e.Errno != nil {
return &e
}
return nil
)
}
// New creates a new security context on the Wayland display at displayPath
@ -65,19 +51,14 @@ func New(displayPath, bindPath *check.Absolute, appID, instanceID string) (*Secu
} else {
closeFds, bindErr := securityContextBindPipe(fd, bindPath, appID, instanceID)
if bindErr != nil {
// securityContextBindPipe does not try to remove the socket during cleanup
closeErr := os.Remove(bindPath.String())
if closeErr != nil && errors.Is(closeErr, os.ErrNotExist) {
closeErr = nil
}
// do not leak the pipe and socket
err = errors.Join(bindErr, // already wrapped
closeErr,
// do not leak the socket
syscall.Close(closeFds[1]),
syscall.Close(closeFds[0]),
syscall.Close(fd),
)
}
return &SecurityContext{closeFds, bindPath}, err
return &SecurityContext{closeFds}, err
}
}

View File

@ -3,7 +3,6 @@ package wayland
import (
"errors"
"os"
"path"
"reflect"
"syscall"
"testing"
@ -12,18 +11,13 @@ import (
)
func TestSecurityContextClose(t *testing.T) {
// do not parallel: fd test not thread safe
t.Parallel()
if err := (*SecurityContext)(nil).Close(); !reflect.DeepEqual(err, os.ErrInvalid) {
t.Fatalf("Close: error = %v", err)
}
var ctx SecurityContext
if f, err := os.Create(path.Join(t.TempDir(), "remove")); err != nil {
t.Fatal(err)
} else {
ctx.bindPath = check.MustAbs(f.Name())
}
if err := syscall.Pipe2(ctx.closeFds[0:], syscall.O_CLOEXEC); err != nil {
t.Fatalf("Pipe: error = %v", err)
}
@ -31,15 +25,9 @@ func TestSecurityContextClose(t *testing.T) {
if err := ctx.Close(); err != nil {
t.Fatalf("Close: error = %v", err)
} else if _, err = os.Stat(ctx.bindPath.String()); err == nil || !errors.Is(err, os.ErrNotExist) {
t.Fatalf("Did not remove %q", ctx.bindPath)
}
wantErr := &Error{Cause: RCleanup, Path: ctx.bindPath.String(), Errno: errors.Join(syscall.EBADF, syscall.EBADF, &os.PathError{
Op: "remove",
Path: ctx.bindPath.String(),
Err: syscall.ENOENT,
})}
wantErr := errors.Join(syscall.EBADF, syscall.EBADF)
if err := ctx.Close(); !reflect.DeepEqual(err, wantErr) {
t.Fatalf("Close: error = %#v, want %#v", err, wantErr)
}

View File

@ -24,8 +24,6 @@ typedef enum {
HAKUREI_WAYLAND_HOST_SOCKET,
/* connect for host server failed, implemented in conn.go */
HAKUREI_WAYLAND_HOST_CONNECT,
/* cleanup failed, implemented in conn.go */
HAKUREI_WAYLAND_CLEANUP,
} hakurei_wayland_res;
hakurei_wayland_res hakurei_security_context_bind(

View File

@ -9,20 +9,14 @@ package wayland
#cgo freebsd openbsd LDFLAGS: -lwayland-client
#include "wayland-client-helper.h"
#include <wayland-version.h>
*/
import "C"
import (
"errors"
"os"
"strings"
"syscall"
)
const (
// Version is the value of WAYLAND_VERSION.
Version = C.WAYLAND_VERSION
// Display contains the name of the server socket
// (https://gitlab.freedesktop.org/wayland/wayland/-/blob/1.23.1/src/wayland-client.c#L1147)
// which is concatenated with XDG_RUNTIME_DIR
@ -85,9 +79,6 @@ const (
RHostSocket Res = C.HAKUREI_WAYLAND_HOST_SOCKET
// RHostConnect is returned if connect failed for host server. Returned by [New].
RHostConnect Res = C.HAKUREI_WAYLAND_HOST_CONNECT
// RCleanup is returned if cleanup fails. Returned by [SecurityContext.Close].
RCleanup Res = C.HAKUREI_WAYLAND_CLEANUP
)
func (e *Error) Unwrap() error { return e.Errno }
@ -129,19 +120,6 @@ func (e *Error) Error() string {
case RHostConnect:
return e.withPrefix("cannot connect to " + e.Host)
case RCleanup:
var pathError *os.PathError
if errors.As(e.Errno, &pathError) && pathError != nil {
return pathError.Error()
}
var errno syscall.Errno
if errors.As(e.Errno, &errno) && errno != 0 {
return "cannot close wayland close_fd pipe: " + errno.Error()
}
return e.withPrefix("cannot hang up wayland security_context")
default:
return e.withPrefix("impossible outcome") /* not reached */
}

View File

@ -58,15 +58,15 @@ func TestError(t *testing.T) {
{"bind", Error{
Cause: RBind,
Path: "/tmp/hakurei.0/18783d07791f2460dbbcffb76c24c9e6/wayland",
Path: "/hakurei.0/18783d07791f2460dbbcffb76c24c9e6/wayland",
Errno: stub.UniqueError(5),
}, "cannot bind /tmp/hakurei.0/18783d07791f2460dbbcffb76c24c9e6/wayland: unique error 5 injected by the test suite"},
}, "cannot bind /hakurei.0/18783d07791f2460dbbcffb76c24c9e6/wayland: unique error 5 injected by the test suite"},
{"listen", Error{
Cause: RListen,
Path: "/tmp/hakurei.0/18783d07791f2460dbbcffb76c24c9e6/wayland",
Path: "/hakurei.0/18783d07791f2460dbbcffb76c24c9e6/wayland",
Errno: stub.UniqueError(6),
}, "cannot listen on /tmp/hakurei.0/18783d07791f2460dbbcffb76c24c9e6/wayland: unique error 6 injected by the test suite"},
}, "cannot listen on /hakurei.0/18783d07791f2460dbbcffb76c24c9e6/wayland: unique error 6 injected by the test suite"},
{"socket invalid", Error{
Cause: RSocket,
@ -92,27 +92,6 @@ func TestError(t *testing.T) {
Errno: stub.UniqueError(8),
}, "cannot connect to /run/user/1971/wayland-1: unique error 8 injected by the test suite"},
{"cleanup", Error{
Cause: RCleanup,
Path: "/tmp/hakurei.0/18783d07791f2460dbbcffb76c24c9e6/wayland",
}, "cannot hang up wayland security_context"},
{"cleanup PathError", Error{
Cause: RCleanup,
Path: "/tmp/hakurei.0/18783d07791f2460dbbcffb76c24c9e6/wayland",
Errno: errors.Join(syscall.EINVAL, &os.PathError{
Op: "remove",
Path: "/tmp/hakurei.0/18783d07791f2460dbbcffb76c24c9e6/wayland",
Err: stub.UniqueError(9),
}),
}, "remove /tmp/hakurei.0/18783d07791f2460dbbcffb76c24c9e6/wayland: unique error 9 injected by the test suite"},
{"cleanup errno", Error{
Cause: RCleanup,
Path: "/tmp/hakurei.0/18783d07791f2460dbbcffb76c24c9e6/wayland",
Errno: errors.Join(syscall.EINVAL),
}, "cannot close wayland close_fd pipe: invalid argument"},
{"invalid", Error{
Cause: 0xbad,
}, "impossible outcome"},